From db2bf66b51d6afaac0a9ad4ef84097d0fc215117 Mon Sep 17 00:00:00 2001 From: HuangHai <10402852@qq.com> Date: Mon, 16 Jun 2025 11:47:20 +0800 Subject: [PATCH] 'commit' --- WebRoot/ai32K.html | 670 ++++++++++++++++++ .../AI32K/Controller/AiController32K.java | 89 +++ .../AI32K/Generator/WordGenerator32K.java | 420 +++++++++++ .../base/AI32K/Model/YunNanModel32K.java | 64 ++ .../base/{AI => AI32K}/TestMax32K.java | 2 +- .../com/dsideal/base/BaseApplication.java | 2 + 6 files changed, 1246 insertions(+), 1 deletion(-) create mode 100644 WebRoot/ai32K.html create mode 100644 src/main/java/com/dsideal/base/AI32K/Controller/AiController32K.java create mode 100644 src/main/java/com/dsideal/base/AI32K/Generator/WordGenerator32K.java create mode 100644 src/main/java/com/dsideal/base/AI32K/Model/YunNanModel32K.java rename src/main/java/com/dsideal/base/{AI => AI32K}/TestMax32K.java (97%) 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);