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);
}