diff --git a/WebRoot/ai32K.html b/WebRoot/ai32K.html
new file mode 100644
index 00000000..e9a86f8e
--- /dev/null
+++ b/WebRoot/ai32K.html
@@ -0,0 +1,670 @@
+
+
+
+
+
+ 云南省市州教育数据AI对比分析器
+
+
+
+
+
云南省市州教育数据AI对比分析器
+
+
+
请选择两个市州进行教育数据对比
+
+
+
+
请选择恰好两个市州
+
+
+
+
+
+
+
+
+
准备就绪
+
+
等待数据...
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/dsideal/base/AI32K/Controller/AiController32K.java b/src/main/java/com/dsideal/base/AI32K/Controller/AiController32K.java
new file mode 100644
index 00000000..a827f7ba
--- /dev/null
+++ b/src/main/java/com/dsideal/base/AI32K/Controller/AiController32K.java
@@ -0,0 +1,89 @@
+package com.dsideal.base.AI32K.Controller;
+
+import com.dsideal.base.AI32K.Generator.WordGenerator32K;
+import com.dsideal.base.AI32K.Model.YunNanModel32K;
+import com.dsideal.base.Util.CallDeepSeek;
+import com.dsideal.base.Util.CommonUtil;
+import com.jfinal.aop.Before;
+import com.jfinal.core.Controller;
+import com.jfinal.ext.interceptor.GET;
+import com.jfinal.kit.PathKit;
+import com.jfinal.kit.SseEmitter;
+import com.jfinal.plugin.activerecord.Record;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+public class AiController32K extends Controller {
+ YunNanModel32K ym = new YunNanModel32K();
+
+ /**
+ * 获取云南所有市州【JSON】
+ */
+ public void getYunNanCity() {
+ List list = ym.getYunNanCity();
+ renderJson(CommonUtil.renderJsonForLayUI(list, list.size()));
+ }
+
+ /**
+ * 对比市州,生成对比报告【Word】
+ *
+ * @param shiZhouA 市州A
+ * @param shiZhouB 市州B
+ * http://10.10.21.20:9200/dsBase/ai32k/compareShiZhouWord?shiZhouA=文山州&shiZhouB=楚雄州
+ */
+ @Before({GET.class})
+ public void compareShiZhouWord(String shiZhouA, String shiZhouB) throws Exception {
+ // 数据获取
+ String content = ym.collectEducationData(new String[]{shiZhouA, shiZhouB});
+ //创建sse
+ final SseEmitter sseEmitter = new SseEmitter(getResponse());
+ // 发送开始分析的消息
+ sseEmitter.sendMessage("data: 开始数据分析...\n\n");
+
+ // 调用DeepSeek API进行流式分析
+ CallDeepSeek.callDeepSeekStream(content, new CallDeepSeek.SSEListener() {
+ @Override
+ public void onData(String data) {
+ sseEmitter.sendMessage("data: " + data + "\n\n"); // 将DeepSeek返回的数据实时推送给前端
+ }
+
+ @Override
+ public void onComplete(String fullResponse) {
+ try {
+ // AI分析完成,开始生成Word文档
+ sseEmitter.sendMessage("data: AI深度分析完成,正在生成报告文档...\n\n");
+ String fileName = "/upload/" + shiZhouA + "与" + shiZhouB + "_教育分析报告_" +
+ new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".docx";
+ String filePath = PathKit.getWebRootPath() + fileName;
+ WordGenerator32K.generateWordDocument(fullResponse, filePath, new String[]{shiZhouA, shiZhouB});
+
+ String baseUrl = CommonUtil.getBaseUrl(getRequest());
+ sseEmitter.sendMessage("data: 报告生成完成!文件Http 相对路径:" + baseUrl + "/dsBase" + fileName + "\n\n");
+ sseEmitter.sendMessage("data: [DONE]\n\n");
+
+ // 完成SSE连接
+ sseEmitter.complete();
+ } catch (Exception e) {
+ sseEmitter.sendMessage("data: 生成报告时发生错误:" + e.getMessage() + "\n\n");
+ sseEmitter.sendMessage("data: [DONE]\n\n");
+ sseEmitter.complete();
+ }
+ }
+
+ @Override
+ public void onError(String error) {
+ // 发送错误信息给前端
+ sseEmitter.sendMessage("data: 调用DeepSeek API异常:" + error + "\n\n");
+ sseEmitter.sendMessage("data: [DONE]\n\n");
+ sseEmitter.complete();
+ }
+ });
+
+ //锁住等待子线程推送完成
+ sseEmitter.waiting(100);
+ renderNull();
+ }
+
+}
diff --git a/src/main/java/com/dsideal/base/AI32K/Generator/WordGenerator32K.java b/src/main/java/com/dsideal/base/AI32K/Generator/WordGenerator32K.java
new file mode 100644
index 00000000..f50ec439
--- /dev/null
+++ b/src/main/java/com/dsideal/base/AI32K/Generator/WordGenerator32K.java
@@ -0,0 +1,420 @@
+package com.dsideal.base.AI32K.Generator;
+
+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.FileOutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Word文档生成器 - 负责生成分析报告的Word文档
+ */
+public class WordGenerator32K {
+
+
+ /**
+ * 生成Word分析报告
+ * @param analysisResult AI分析结果
+ * @param fileName 文件名(不包含路径)
+ * @param regions 对比地区
+ * @throws Exception 文件操作异常
+ */
+ public static void generateWordDocument(String analysisResult, String fileName, String[] regions) throws Exception {
+ XWPFDocument document = new XWPFDocument();
+ try {
+ // 动态生成标题
+ String reportTitle = generateReportTitle(regions);
+
+ // 添加文档标题
+ addDocumentTitle(document, reportTitle);
+
+ // 添加文档信息
+ addDocumentInfo(document);
+
+ // 添加分析内容
+ addAnalysisContent(document, analysisResult);
+
+ // 添加文档结尾
+ addDocumentFooter(document);
+
+ // 保存文档
+ try (FileOutputStream out = new FileOutputStream(fileName)) {
+ document.write(out);
+ }
+
+ } finally {
+ document.close();
+ }
+ }
+
+ /**
+ * 生成报告标题
+ */
+ private static String generateReportTitle(String[] regions) {
+ if (regions != null && regions.length >= 2) {
+ return String.join("与", regions) + "教育资源配置对比分析报告";
+ } else {
+ return "教育资源配置对比分析报告";
+ }
+ }
+
+ /**
+ * 添加文档标题
+ */
+ private static void addDocumentTitle(XWPFDocument document, String title) {
+ XWPFParagraph titleParagraph = document.createParagraph();
+ titleParagraph.setAlignment(ParagraphAlignment.CENTER);
+ XWPFRun titleRun = titleParagraph.createRun();
+ titleRun.setText(title);
+ titleRun.setBold(true);
+ titleRun.setFontSize(18);
+ titleRun.setFontFamily("宋体");
+
+ // 添加空行
+ document.createParagraph();
+ }
+
+ /**
+ * 添加文档信息
+ */
+ private static void addDocumentInfo(XWPFDocument document) {
+ XWPFParagraph infoParagraph = document.createParagraph();
+ XWPFRun infoRun = infoParagraph.createRun();
+ infoRun.setText("生成时间:" + new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date()));
+ infoRun.setFontSize(12);
+ infoRun.setFontFamily("宋体");
+
+ XWPFRun toolRun = infoParagraph.createRun();
+ toolRun.addBreak();
+ toolRun.setText("分析工具:DeepSeek AI");
+ toolRun.setFontSize(12);
+ toolRun.setFontFamily("宋体");
+
+ // 添加分隔线
+ XWPFParagraph separatorParagraph = document.createParagraph();
+ XWPFRun separatorRun = separatorParagraph.createRun();
+ separatorRun.setText("=".repeat(50));
+ separatorRun.setFontSize(10);
+
+ // 添加空行
+ document.createParagraph();
+ }
+
+ /**
+ * 添加分析内容 - 使用CommonMark解析Markdown
+ */
+ private static void addAnalysisContent(XWPFDocument document, String analysisResult) {
+ // 创建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 (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();
+ }
+ }
+ 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();
+ }
+ }
+ }
+
+ /**
+ * 判断是否为标题行
+ */
+ private static boolean isTitle(String line) {
+ return line.matches("^\\d+\\..+") ||
+ line.matches("^[一二三四五六七八九十]+、.+") ||
+ line.trim().matches("^(执行摘要|数据概览|详细对比分析|问题识别|建议与对策|结论).*");
+ }
+
+ /**
+ * 添加文档结尾
+ */
+ private static void addDocumentFooter(XWPFDocument document) {
+ XWPFParagraph endParagraph = document.createParagraph();
+ endParagraph.createRun().addBreak();
+ XWPFRun endSeparatorRun = endParagraph.createRun();
+ endSeparatorRun.setText("=".repeat(50));
+ endSeparatorRun.setFontSize(10);
+
+ XWPFRun endRun = endParagraph.createRun();
+ endRun.addBreak();
+ endRun.setText("报告结束");
+ endRun.setFontSize(12);
+ endRun.setFontFamily("宋体");
+ endRun.setBold(true);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/dsideal/base/AI32K/Model/YunNanModel32K.java b/src/main/java/com/dsideal/base/AI32K/Model/YunNanModel32K.java
new file mode 100644
index 00000000..57ede09d
--- /dev/null
+++ b/src/main/java/com/dsideal/base/AI32K/Model/YunNanModel32K.java
@@ -0,0 +1,64 @@
+package com.dsideal.base.AI32K.Model;
+
+import cn.hutool.json.JSONUtil;
+import com.dsideal.base.DataEase.Model.DataEaseModel;
+import com.jfinal.plugin.activerecord.Db;
+import com.jfinal.plugin.activerecord.Record;
+
+import java.util.List;
+
+public class YunNanModel32K {
+ /**
+ * 收集指定地区的教育资源配置数据
+ *
+ * @param regions 要对比的地区数组
+ * @return 格式化的数据内容
+ */
+ public String collectEducationData(String[] regions) {
+ // 查询教育资源配置发展预测相关表【低于32K】
+ String sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dataease' AND TABLE_NAME LIKE 'excel_报告-教育资源配置发展预测%';";
+
+ // 超过32K
+ //String sql="select table_name as TABLE_NAME from core_dataset_table where dataset_group_id in (select id from core_dataset_group where pid='1036317909951057920')";
+ List tableList = Db.use(DataEaseModel.DB_NAME).find(sql);
+
+ StringBuilder dataContent = new StringBuilder();
+
+ // 构建数据标题
+ dataContent.append("教育资源配置发展预测数据对比分析\n\n");
+ dataContent.append("对比州市:").append(String.join(" vs ", regions)).append("\n\n");
+
+ // 遍历所有相关数据表
+ for (Record record : tableList) {
+ String tableName = record.getStr("TABLE_NAME");
+ dataContent.append("数据表:").append(tableName).append("\n");
+
+ // 为每个地区收集数据
+ for (String region : regions) {
+ sql = "select * from `" + tableName + "` where `行政区划`=?";
+ List listContent = Db.use(DataEaseModel.DB_NAME).find(sql, region);
+
+ if (!listContent.isEmpty()) {
+ dataContent.append("\n").append(region).append("数据:\n");
+ for (Record dataRecord : listContent) {
+ dataContent.append(JSONUtil.toJsonPrettyStr(dataRecord.getColumns())).append("\n");
+ }
+ } else {
+ dataContent.append("\n").append(region).append(":无相关数据\n");
+ }
+ }
+ dataContent.append("\n----------------------------------------\n\n");
+ }
+ return dataContent.toString();
+ }
+
+ /**
+ * 获取云南省下所有城市名称
+ *
+ * @return
+ */
+ public List getYunNanCity() {
+ String sql = "select id,area_code,area_name,full_name from t_dm_area where parent_id='FD61813E-70A1-42AB-9A8E-141ED4D47B98'";
+ return Db.find(sql);
+ }
+}
diff --git a/src/main/java/com/dsideal/base/AI/TestMax32K.java b/src/main/java/com/dsideal/base/AI32K/TestMax32K.java
similarity index 97%
rename from src/main/java/com/dsideal/base/AI/TestMax32K.java
rename to src/main/java/com/dsideal/base/AI32K/TestMax32K.java
index 7b245587..4979a88a 100644
--- a/src/main/java/com/dsideal/base/AI/TestMax32K.java
+++ b/src/main/java/com/dsideal/base/AI32K/TestMax32K.java
@@ -1,4 +1,4 @@
-package com.dsideal.base.AI;
+package com.dsideal.base.AI32K;
import com.dsideal.base.AI.Generator.PptGenerator;
import com.dsideal.base.DataEase.Model.DataEaseModel;
diff --git a/src/main/java/com/dsideal/base/BaseApplication.java b/src/main/java/com/dsideal/base/BaseApplication.java
index a5706c7b..505126f2 100644
--- a/src/main/java/com/dsideal/base/BaseApplication.java
+++ b/src/main/java/com/dsideal/base/BaseApplication.java
@@ -2,6 +2,7 @@ package com.dsideal.base;
import com.dsideal.base.AI.Controller.SSEController;
import com.dsideal.base.AI.Controller.AiController;
+import com.dsideal.base.AI32K.Controller.AiController32K;
import com.dsideal.base.Base.Controller.BaseController;
import com.dsideal.base.Classes.Controller.ClassController;
import com.dsideal.base.DataAuthority.Controller.DataAuthorityController;
@@ -104,6 +105,7 @@ public class BaseApplication extends JFinalConfig {
me.add("/yx", YunXiaoController.class);
//大数据AI
me.add("/ai", AiController.class);
+ me.add("/ai32k", AiController32K.class);
//SSE测试
me.add("/sse", SSEController.class);