diff --git a/WebRoot/upload/文山州与楚雄州_教育分析报告_20250614_064949.docx b/WebRoot/upload/文山州与楚雄州_教育分析报告_20250614_064949.docx new file mode 100644 index 00000000..23a9f91d Binary files /dev/null and b/WebRoot/upload/文山州与楚雄州_教育分析报告_20250614_064949.docx differ diff --git a/WebRoot/upload/文山州与楚雄州_教育分析报告_20250614_065731.docx b/WebRoot/upload/文山州与楚雄州_教育分析报告_20250614_065731.docx new file mode 100644 index 00000000..cb15808a Binary files /dev/null and b/WebRoot/upload/文山州与楚雄州_教育分析报告_20250614_065731.docx differ diff --git a/pom.xml b/pom.xml index b1f0931b..cd08476d 100644 --- a/pom.xml +++ b/pom.xml @@ -321,6 +321,23 @@ poi-scratchpad 5.2.5 + + + + org.commonmark + commonmark + 0.21.0 + + + org.commonmark + commonmark-ext-gfm-tables + 0.21.0 + + + org.commonmark + commonmark-ext-heading-anchor + 0.21.0 + diff --git a/src/main/java/com/dsideal/base/AI/Generator/WordGenerator.java b/src/main/java/com/dsideal/base/AI/Generator/WordGenerator.java index e98fbadc..fed6fd68 100644 --- a/src/main/java/com/dsideal/base/AI/Generator/WordGenerator.java +++ b/src/main/java/com/dsideal/base/AI/Generator/WordGenerator.java @@ -4,11 +4,20 @@ import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.ParagraphAlignment; +import org.apache.poi.xwpf.usermodel.XWPFTable; +import org.apache.poi.xwpf.usermodel.XWPFTableRow; +import org.apache.poi.xwpf.usermodel.XWPFTableCell; +import org.commonmark.node.*; +import org.commonmark.parser.Parser; +import org.commonmark.ext.gfm.tables.TablesExtension; +import org.commonmark.ext.heading.anchor.HeadingAnchorExtension; import java.io.File; import java.io.FileOutputStream; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; +import java.util.List; /** * Word文档生成器 - 负责生成分析报告的Word文档 @@ -105,52 +114,280 @@ public class WordGenerator { } /** - * 添加分析内容 + * 添加分析内容 - 使用CommonMark解析Markdown */ private static void addAnalysisContent(XWPFDocument document, String analysisResult) { - String[] lines = analysisResult.split("\n"); - XWPFParagraph currentParagraph = null; - XWPFRun currentRun = null; - - for (String line : lines) { - if (line.trim().isEmpty()) { - // 空行,创建新段落 - document.createParagraph(); - currentParagraph = null; - currentRun = null; - } else { - // 检查是否是标题行 - boolean isTitle = isTitle(line); - - if (currentParagraph == null) { - currentParagraph = document.createParagraph(); - currentRun = currentParagraph.createRun(); - currentRun.setFontFamily("宋体"); - currentRun.setFontSize(12); + // 创建CommonMark解析器,支持表格和标题锚点扩展 + List extensions = Arrays.asList( + TablesExtension.create(), + HeadingAnchorExtension.create() + ); + Parser parser = Parser.builder().extensions(extensions).build(); + + // 解析Markdown文档 + Node markdownDoc = parser.parse(analysisResult); + + // 遍历并处理所有节点 + processMarkdownNode(document, markdownDoc); + } + + /** + * 处理Markdown节点并转换为Word格式 + */ + private static void processMarkdownNode(XWPFDocument document, Node node) { + Node child = node.getFirstChild(); + while (child != null) { + if (child instanceof Heading) { + processHeading(document, (Heading) child); + } else if (child instanceof Paragraph) { + processParagraph(document, (Paragraph) child); + } else if (child instanceof BulletList) { + processBulletList(document, (BulletList) child); + } else if (child instanceof OrderedList) { + processOrderedList(document, (OrderedList) child); + } else if (child instanceof org.commonmark.ext.gfm.tables.TableBlock) { + processTable(document, (org.commonmark.ext.gfm.tables.TableBlock) child); + } else if (child instanceof BlockQuote) { + processBlockQuote(document, (BlockQuote) child); + } else if (child instanceof FencedCodeBlock || child instanceof IndentedCodeBlock) { + processCodeBlock(document, child); + } + child = child.getNext(); + } + } + + /** + * 处理标题 + */ + private static void processHeading(XWPFDocument document, Heading heading) { + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + + String text = getTextContent(heading); + run.setText(text); + run.setBold(true); + run.setFontFamily("宋体"); + + // 根据标题级别设置字体大小 + switch (heading.getLevel()) { + case 1: + run.setFontSize(18); + paragraph.setAlignment(ParagraphAlignment.CENTER); + break; + case 2: + run.setFontSize(16); + break; + case 3: + run.setFontSize(14); + break; + case 4: + run.setFontSize(13); + break; + default: + run.setFontSize(12); + break; + } + } + + /** + * 处理段落 + */ + private static void processParagraph(XWPFDocument document, Paragraph para) { + XWPFParagraph paragraph = document.createParagraph(); + processInlineNodes(paragraph, para.getFirstChild()); + } + + /** + * 处理无序列表 + */ + private static void processBulletList(XWPFDocument document, BulletList bulletList) { + Node listItem = bulletList.getFirstChild(); + while (listItem != null) { + if (listItem instanceof ListItem) { + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setText("• " + getTextContent(listItem)); + run.setFontSize(12); + run.setFontFamily("宋体"); + } + listItem = listItem.getNext(); + } + } + + /** + * 处理有序列表 + */ + private static void processOrderedList(XWPFDocument document, OrderedList orderedList) { + Node listItem = orderedList.getFirstChild(); + int counter = orderedList.getStartNumber(); + while (listItem != null) { + if (listItem instanceof ListItem) { + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setText(counter + ". " + getTextContent(listItem)); + run.setFontSize(12); + run.setFontFamily("宋体"); + counter++; + } + listItem = listItem.getNext(); + } + } + + /** + * 处理表格 + */ + private static void processTable(XWPFDocument document, org.commonmark.ext.gfm.tables.TableBlock table) { + // 计算表格列数 + int cols = 0; + Node firstRow = table.getFirstChild(); + if (firstRow instanceof org.commonmark.ext.gfm.tables.TableHead) { + Node headerRow = firstRow.getFirstChild(); + if (headerRow instanceof org.commonmark.ext.gfm.tables.TableRow) { + Node cell = headerRow.getFirstChild(); + while (cell != null) { + cols++; + cell = cell.getNext(); } - - if (isTitle) { - // 如果当前段落已有内容,创建新段落 - String currentText = currentRun.getText(0); - if (currentText != null && !currentText.isEmpty()) { - currentParagraph = document.createParagraph(); - currentRun = currentParagraph.createRun(); - currentRun.setFontFamily("宋体"); - } - currentRun.setText(line); - currentRun.setBold(true); - currentRun.setFontSize(14); - currentParagraph = null; // 强制下一行创建新段落 - currentRun = null; - } else { - // 普通内容 - String currentText = currentRun.getText(0); - if (currentText != null && !currentText.isEmpty()) { - currentRun.addBreak(); + } + } + + if (cols > 0) { + XWPFTable wordTable = document.createTable(); + + Node tableChild = table.getFirstChild(); + boolean isFirstRow = true; + while (tableChild != null) { + if (tableChild instanceof org.commonmark.ext.gfm.tables.TableHead || + tableChild instanceof org.commonmark.ext.gfm.tables.TableBody) { + + Node rowNode = tableChild.getFirstChild(); + while (rowNode != null) { + if (rowNode instanceof org.commonmark.ext.gfm.tables.TableRow) { + XWPFTableRow row; + if (isFirstRow) { + row = wordTable.getRow(0); + isFirstRow = false; + } else { + row = wordTable.createRow(); + } + + Node cellNode = rowNode.getFirstChild(); + int cellIndex = 0; + while (cellNode != null && cellIndex < cols) { + if (cellNode instanceof org.commonmark.ext.gfm.tables.TableCell) { + XWPFTableCell cell = row.getCell(cellIndex); + if (cell == null) { + cell = row.addNewTableCell(); + } + cell.setText(getTextContent(cellNode)); + cellIndex++; + } + cellNode = cellNode.getNext(); + } + } + rowNode = rowNode.getNext(); } - currentRun.setText(line); - currentRun.setFontSize(12); } + tableChild = tableChild.getNext(); + } + } + } + + /** + * 处理引用块 + */ + private static void processBlockQuote(XWPFDocument document, BlockQuote blockQuote) { + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setText("" + getTextContent(blockQuote)); + run.setFontSize(12); + run.setFontFamily("宋体"); + run.setItalic(true); + } + + /** + * 处理代码块 + */ + private static void processCodeBlock(XWPFDocument document, Node codeBlock) { + XWPFParagraph paragraph = document.createParagraph(); + XWPFRun run = paragraph.createRun(); + + String code; + if (codeBlock instanceof FencedCodeBlock) { + code = ((FencedCodeBlock) codeBlock).getLiteral(); + } else { + code = ((IndentedCodeBlock) codeBlock).getLiteral(); + } + + run.setText(code); + run.setFontSize(10); + run.setFontFamily("Courier New"); + // 设置背景色(如果支持) + } + + /** + * 处理内联节点(粗体、斜体、链接等) + */ + private static void processInlineNodes(XWPFParagraph paragraph, Node node) { + while (node != null) { + if (node instanceof Text) { + XWPFRun run = paragraph.createRun(); + run.setText(((Text) node).getLiteral()); + run.setFontSize(12); + run.setFontFamily("宋体"); + } else if (node instanceof StrongEmphasis) { + XWPFRun run = paragraph.createRun(); + run.setText(getTextContent(node)); + run.setBold(true); + run.setFontSize(12); + run.setFontFamily("宋体"); + } else if (node instanceof Emphasis) { + XWPFRun run = paragraph.createRun(); + run.setText(getTextContent(node)); + run.setItalic(true); + run.setFontSize(12); + run.setFontFamily("宋体"); + } else if (node instanceof Code) { + XWPFRun run = paragraph.createRun(); + run.setText(((Code) node).getLiteral()); + run.setFontSize(10); + run.setFontFamily("Courier New"); + } else if (node instanceof Link) { + XWPFRun run = paragraph.createRun(); + run.setText(getTextContent(node) + " (" + ((Link) node).getDestination() + ")"); + run.setFontSize(12); + run.setFontFamily("宋体"); + run.setColor("0000FF"); + } else { + // 递归处理子节点 + processInlineNodes(paragraph, node.getFirstChild()); + } + node = node.getNext(); + } + } + + /** + * 获取节点的文本内容 + */ + private static String getTextContent(Node node) { + StringBuilder text = new StringBuilder(); + extractText(node, text); + return text.toString().trim(); + } + + /** + * 递归提取文本内容 + */ + private static void extractText(Node node, StringBuilder text) { + if (node instanceof Text) { + text.append(((Text) node).getLiteral()); + } else if (node instanceof Code) { + text.append(((Code) node).getLiteral()); + } else { + Node child = node.getFirstChild(); + while (child != null) { + extractText(child, text); + child = child.getNext(); } } } diff --git a/src/main/java/com/dsideal/base/AI/Model/YunNanModel.java b/src/main/java/com/dsideal/base/AI/Model/YunNanModel.java index a4ecbb61..0062ac89 100644 --- a/src/main/java/com/dsideal/base/AI/Model/YunNanModel.java +++ b/src/main/java/com/dsideal/base/AI/Model/YunNanModel.java @@ -65,20 +65,20 @@ public class YunNanModel { public String createAnalysisPrompt(String dataContent, String[] regions) { String regionNames = String.join("和", regions); return "你是一位专业的教育数据分析专家,请基于以下数据对" + regionNames + "的教育资源配置情况进行深入对比分析。\n\n" + - "重要要求:请生成结构化的纯文本格式报告,使用清晰的段落结构,不要使用markdown语法。\n\n" + + "重要要求:请生成结构化的Markdown格式报告,使用标准的Markdown语法,包括标题、列表、粗体等格式。\n\n" + "请按照以下结构进行分析并生成专业的分析报告:\n\n" + - "1. 执行摘要\n" + - " 简要概述两州教育资源配置的整体情况和主要发现\n\n" + - "2. 数据概览\n" + - " 两州基本教育数据对比和关键指标汇总\n\n" + - "3. 详细对比分析\n" + - " 教育资源配置水平对比、发展趋势分析、优势与不足分析\n\n" + - "4. 问题识别\n" + - " 存在的主要问题和资源配置不均衡情况\n\n" + - "5. 建议与对策\n" + - " 针对性改进建议和资源优化配置方案\n\n" + - "6. 结论\n" + - " 总体评价和未来发展方向\n\n" + + "## 1. 执行摘要\n" + + "简要概述两州教育资源配置的整体情况和主要发现\n\n" + + "## 2. 数据概览\n" + + "两州基本教育数据对比和关键指标汇总\n\n" + + "## 3. 详细对比分析\n" + + "教育资源配置水平对比、发展趋势分析、优势与不足分析\n\n" + + "## 4. 问题识别\n" + + "存在的主要问题和资源配置不均衡情况\n\n" + + "## 5. 建议与对策\n" + + "针对性改进建议和资源优化配置方案\n\n" + + "## 6. 结论\n" + + "总体评价和未来发展方向\n\n" + "请确保分析客观、专业,数据引用准确,建议具有可操作性。\n\n" + "=== 原始数据 ===\n" + dataContent; } diff --git a/src/main/java/com/dsideal/base/Util/CallDeepSeek.java b/src/main/java/com/dsideal/base/Util/CallDeepSeek.java index bde0f252..889d50b2 100644 --- a/src/main/java/com/dsideal/base/Util/CallDeepSeek.java +++ b/src/main/java/com/dsideal/base/Util/CallDeepSeek.java @@ -148,6 +148,7 @@ public class CallDeepSeek { if (saveToFile && outputPath != null) { FileUtil.writeString(responseContent, new File(outputPath), "UTF-8"); listener.onComplete("内容已成功保存到" + outputPath); + System.out.println("内容已成功保存到" + outputPath); } else { listener.onComplete(responseContent); }