Browse Source

1、测试优化ai智能报表

dev_bl_new
25604 2 weeks ago
parent
commit
0579af014f
  1. 84
      mh-admin/src/main/java/com/mh/web/controller/ai/AiFileController.java
  2. 5
      mh-admin/src/main/java/com/mh/web/controller/ai/AsyncReportController.java
  3. 39
      mh-admin/src/main/java/com/mh/web/controller/ai/FileDownloadController.java
  4. 2
      mh-admin/src/main/resources/application-dev.yml
  5. 4
      mh-common/src/main/java/com/mh/common/config/FileStorageUtil.java
  6. 22
      mh-common/src/main/java/com/mh/common/core/domain/dto/AsyncTaskEvent.java
  7. 7
      mh-framework/src/main/java/com/mh/framework/config/ResourcesConfig.java
  8. 7
      mh-framework/src/main/java/com/mh/framework/config/SecurityConfig.java
  9. 471
      mh-system/src/main/java/com/mh/system/service/ai/impl/AdvancedWordGenerationServiceImpl.java
  10. 44
      mh-system/src/main/java/com/mh/system/service/ai/impl/AsyncReportServiceImpl.java
  11. 4
      mh-system/src/main/java/com/mh/system/service/ai/impl/EnergyReportServiceImpl.java
  12. 23
      mh-system/src/main/java/com/mh/system/service/ai/impl/ReportOrchestrationService.java
  13. 33
      mh-system/src/main/java/com/mh/system/service/ai/impl/TaskStoreService.java

84
mh-admin/src/main/java/com/mh/web/controller/ai/AiFileController.java

@ -0,0 +1,84 @@
package com.mh.web.controller.ai;
import com.mh.common.config.MHConfig;
import com.mh.common.core.controller.BaseController;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* AI文件下载控制器
* 处理AI生成的报表文件下载请求
*
* @author mh
*/
@RestController
@RequestMapping("/ai/files")
public class AiFileController extends BaseController {
/**
* 下载AI生成的文件
*
* @param filename 文件名
* @return 文件响应
*/
@GetMapping("/{filename:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
return getResourceResponseEntity(filename);
}
private ResponseEntity<Resource> getResourceResponseEntity(@PathVariable String filename) {
try {
// 构建文件路径
String storageDir = MHConfig.getProfile();
File file = new File(storageDir, filename);
// 检查文件是否存在
if (!file.exists()) {
return ResponseEntity.notFound().build();
}
// 创建资源
Resource resource = new FileSystemResource(file);
// 使用RFC 5987标准格式,支持UTF-8编码
String encodedFileName = URLEncoder.encode(filename, StandardCharsets.UTF_8);
String disposition = String.format("attachment; filename*=UTF-8''%s", encodedFileName);
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, disposition);
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
/**
* 设备分析导出
*
* @param filename 文件名
* @return 文件响应
*/
@PostMapping("/{filename:.+}")
public ResponseEntity<Resource> deviceAnalyzeExport(@PathVariable String filename) {
return getResourceResponseEntity(filename);
}
}

5
mh-admin/src/main/java/com/mh/web/controller/ai/AsyncReportController.java

@ -1,5 +1,6 @@
package com.mh.web.controller.ai; package com.mh.web.controller.ai;
import com.mh.common.core.controller.BaseController;
import com.mh.common.core.domain.dto.AsyncTaskDTO; import com.mh.common.core.domain.dto.AsyncTaskDTO;
import com.mh.system.service.ai.impl.AsyncReportServiceImpl; import com.mh.system.service.ai.impl.AsyncReportServiceImpl;
import com.mh.system.service.ai.impl.TaskStoreService; import com.mh.system.service.ai.impl.TaskStoreService;
@ -18,8 +19,8 @@ import java.util.Map;
* @date 2026-02-27 11:36:57 * @date 2026-02-27 11:36:57
*/ */
@RestController @RestController
@RequestMapping("/api/reports/async") @RequestMapping("/ai/reports/async")
public class AsyncReportController { public class AsyncReportController extends BaseController {
private final AsyncReportServiceImpl asyncReportService; private final AsyncReportServiceImpl asyncReportService;
private final TaskStoreService taskStore; private final TaskStoreService taskStore;

39
mh-admin/src/main/java/com/mh/web/controller/ai/FileDownloadController.java

@ -1,39 +0,0 @@
package com.mh.web.controller.ai;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* @author LJF
* @version 1.0
* @project EEMCS
* @description 文件下载链接
* @date 2026-02-27 11:40:07
*/
@RequestMapping("/api/files")
public class FileDownloadController {
@Value("${mh.profile:/tmp/energy-reports}")
private String storageDir;
@GetMapping("/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws Exception {
Path filePath = Paths.get(storageDir).resolve(filename);
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.body(resource);
}
return ResponseEntity.notFound().build();
}
}

2
mh-admin/src/main/resources/application-dev.yml

@ -107,7 +107,7 @@ spring:
# 主库数据源 # 主库数据源
master: master:
#添加allowMultiQueries=true 在批量更新时才不会出错 #添加allowMultiQueries=true 在批量更新时才不会出错
url: jdbc:postgresql://127.0.0.1:5432/eemcs_gh_ers_dev url: jdbc:postgresql://127.0.0.1:5432/eemcs
# url: jdbc:postgresql://106.55.173.225:5505/eemcs # url: jdbc:postgresql://106.55.173.225:5505/eemcs
username: postgres username: postgres
password: mh@803 password: mh@803

4
mh-common/src/main/java/com/mh/common/config/FileStorageUtil.java

@ -18,7 +18,7 @@ import java.nio.file.Paths;
*/ */
@Component @Component
public class FileStorageUtil { public class FileStorageUtil {
@Value("${mh.profile:/tmp/energy-reports}") @Value("${mh.profile:/}")
private String storageDir; private String storageDir;
public String saveFile(String filename, byte[] content) throws IOException { public String saveFile(String filename, byte[] content) throws IOException {
@ -28,7 +28,7 @@ public class FileStorageUtil {
Files.write(filePath, content); Files.write(filePath, content);
// 生成可下载的URL(假设当前应用提供静态资源访问或下载接口) // 生成可下载的URL(假设当前应用提供静态资源访问或下载接口)
return ServletUriComponentsBuilder.fromCurrentContextPath() return ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/files/") .path("/ai/files/")
.path(filename) .path(filename)
.toUriString(); .toUriString();
} }

22
mh-common/src/main/java/com/mh/common/core/domain/dto/AsyncTaskEvent.java

@ -0,0 +1,22 @@
package com.mh.common.core.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author LJF
* @version 1.0
* @project EEMCS
* @description 异步任务事件dto
* @date 2026-02-28
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AsyncTaskEvent {
private String event; // 事件类型:reasoning, COMPLETED, FAILED
private Object data; // 事件数据
}

7
mh-framework/src/main/java/com/mh/framework/config/ResourcesConfig.java

@ -36,7 +36,12 @@ public class ResourcesConfig implements WebMvcConfigurer
/** swagger配置 */ /** swagger配置 */
registry.addResourceHandler("/swagger-ui/**") registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());; .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());
/** AI文件下载路径 */
registry.addResourceHandler("/ai/files/**")
.addResourceLocations("file:" + MHConfig.getProfile() + "/")
.setCacheControl(CacheControl.noCache());;
} }
/** /**

7
mh-framework/src/main/java/com/mh/framework/config/SecurityConfig.java

@ -114,7 +114,12 @@ public class SecurityConfig
requests.requestMatchers("/login", "/register", "/captchaImage").permitAll() requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
// 静态资源,可匿名访问 // 静态资源,可匿名访问
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll() .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
.requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll() // .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll()
// 文件下载路径,允许匿名访问
.requestMatchers(HttpMethod.GET, "/ai/files/**").permitAll()
// AI报表异步处理路径,允许匿名访问
.requestMatchers(HttpMethod.POST, "/ai/reports/async").permitAll()
.requestMatchers(HttpMethod.GET, "/ai/reports/async/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证 // 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated(); .anyRequest().authenticated();
}) })

471
mh-system/src/main/java/com/mh/system/service/ai/impl/AdvancedWordGenerationServiceImpl.java

@ -0,0 +1,471 @@
package com.mh.system.service.ai.impl;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.*;
import com.mh.common.core.domain.dto.EnergyReportDTO;
import com.mh.common.core.domain.dto.ReportRequestDTO;
import com.mh.system.service.ai.IFileGenerationService;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
/**
* @author MH
* @version 2.0
* @project EEMCS
* @description 高级Word文档生成服务 - 支持表格和水印
* @date 2026-02-28
*/
@Service
public class AdvancedWordGenerationServiceImpl implements IFileGenerationService {
@Override
public byte[] generateReport(EnergyReportDTO report, ReportRequestDTO request) throws IOException {
try (XWPFDocument document = new XWPFDocument()) {
// 添加专业水印
if (request.getWatermark()) {
addProfessionalWatermark(document, request.getWatermarkText());
}
// 创建专业的文档结构
createProfessionalDocument(document, report);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
document.write(baos);
return baos.toByteArray();
}
}
/**
* 创建专业的文档结构
*/
private void createProfessionalDocument(XWPFDocument document, EnergyReportDTO report) {
// 添加封面页
addCoverPage(document, report);
// 添加目录(可选)
addTableOfContents(document);
// 添加正文内容
addMainContent(document, report);
}
/**
* 添加封面页
*/
private void addCoverPage(XWPFDocument document, EnergyReportDTO report) {
// 标题
XWPFParagraph titlePara = document.createParagraph();
titlePara.setAlignment(ParagraphAlignment.CENTER);
XWPFRun titleRun = titlePara.createRun();
titleRun.setText("中央空调能效分析报告");
titleRun.setBold(true);
titleRun.setFontSize(28);
titleRun.addBreak();
// 副标题
XWPFParagraph subtitlePara = document.createParagraph();
subtitlePara.setAlignment(ParagraphAlignment.CENTER);
XWPFRun subtitleRun = subtitlePara.createRun();
subtitleRun.setText("Central Air Conditioning Energy Efficiency Analysis Report");
subtitleRun.setFontSize(16);
subtitleRun.setColor("666666");
subtitleRun.addBreak();
subtitleRun.addBreak();
// 报告日期
XWPFParagraph datePara = document.createParagraph();
datePara.setAlignment(ParagraphAlignment.CENTER);
XWPFRun dateRun = datePara.createRun();
dateRun.setText("报告日期: " + report.getReportDate());
dateRun.setFontSize(14);
dateRun.addBreak();
dateRun.addBreak();
dateRun.addBreak();
// 添加分页
document.createParagraph().createRun().addBreak(BreakType.PAGE);
}
/**
* 添加目录
*/
private void addTableOfContents(XWPFDocument document) {
XWPFParagraph tocTitle = document.createParagraph();
tocTitle.setAlignment(ParagraphAlignment.CENTER);
XWPFRun tocRun = tocTitle.createRun();
tocRun.setText("目 录");
tocRun.setBold(true);
tocRun.setFontSize(18);
tocRun.addBreak();
// 目录项
addTocItem(document, "1. 基本信息", 1);
addTocItem(document, "2. 设备能效明细", 1);
addTocItem(document, "3. 异常情况分析", 1);
addTocItem(document, "4. 优化建议", 1);
document.createParagraph().createRun().addBreak(BreakType.PAGE);
}
private void addTocItem(XWPFDocument document, String text, int level) {
XWPFParagraph para = document.createParagraph();
if (level > 1) {
para.setIndentationLeft(400);
}
XWPFRun run = para.createRun();
run.setText(text);
run.setFontSize(12);
}
/**
* 添加主要内容
*/
private void addMainContent(XWPFDocument document, EnergyReportDTO report) {
// 第一章:基本信息
addChapter(document, "第一章 基本信息", report);
// 第二章:设备能效明细(包含表格)
addDeviceDetailsChapter(document, report);
// 第三章:异常情况分析
addAnomaliesChapter(document, report);
// 第四章:优化建议
addSuggestionsChapter(document, report);
}
/**
* 添加章节标题
*/
private void addChapter(XWPFDocument document, String title, EnergyReportDTO report) {
XWPFParagraph chapterPara = document.createParagraph();
chapterPara.setStyle("Heading1");
XWPFRun chapterRun = chapterPara.createRun();
chapterRun.setText(title);
chapterRun.setBold(true);
chapterRun.setFontSize(16);
chapterRun.addBreak();
// 添加基本信息内容
addBasicInfoContent(document, report);
document.createParagraph().createRun().addBreak();
}
/**
* 添加基本信息内容
*/
private void addBasicInfoContent(XWPFDocument document, EnergyReportDTO report) {
String[] infoItems = {
"报表日期: " + report.getReportDate(),
"整体能效评分: " + report.getOverallScore(),
"系统综合能效比(EER): " + report.getSystemEER(),
"总能耗: " + formatDouble(report.getTotalEnergyConsumption()) + " kWh"
};
for (String item : infoItems) {
XWPFParagraph para = document.createParagraph();
para.setIndentationLeft(400);
XWPFRun run = para.createRun();
run.setText("• " + item);
run.setFontSize(12);
}
}
/**
* 添加设备明细章节包含专业表格
*/
private void addDeviceDetailsChapter(XWPFDocument document, EnergyReportDTO report) {
XWPFParagraph chapterPara = document.createParagraph();
chapterPara.setStyle("Heading1");
XWPFRun chapterRun = chapterPara.createRun();
chapterRun.setText("第二章 设备能效明细");
chapterRun.setBold(true);
chapterRun.setFontSize(16);
chapterRun.addBreak();
EnergyReportDTO.DeviceEnergy[] devices = report.getDeviceDetails().toArray(new EnergyReportDTO.DeviceEnergy[0]);
if (devices != null && devices.length > 0) {
createDeviceDetailsTable(document, devices);
} else {
XWPFParagraph para = document.createParagraph();
para.setIndentationLeft(400);
XWPFRun run = para.createRun();
run.setText("暂无设备数据");
run.setFontSize(12);
}
document.createParagraph().createRun().addBreak();
}
/**
* 创建专业的设备明细表格
*/
private void createDeviceDetailsTable(XWPFDocument document, EnergyReportDTO.DeviceEnergy[] devices) {
// 创建表格 (行数 = 表头1行 + 数据行数)
int rows = devices.length + 1;
int cols = 5;
XWPFTable table = document.createTable(rows, cols);
// 设置表格样式
setTableStyle(table);
// 设置表头
String[] headers = {"设备名称", "设备类型", "能耗(kWh)", "效率(%)", "偏差率(%)"};
XWPFTableRow headerRow = table.getRow(0);
for (int i = 0; i < headers.length; i++) {
XWPFTableCell cell = headerRow.getCell(i);
cell.setColor("3366FF");
XWPFParagraph para = cell.getParagraphs().get(0);
para.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = para.createRun();
run.setText(headers[i]);
run.setBold(true);
run.setColor("FFFFFF");
run.setFontSize(12);
}
// 填充数据
for (int i = 0; i < devices.length; i++) {
EnergyReportDTO.DeviceEnergy device = devices[i];
XWPFTableRow row = table.getRow(i + 1);
String[] values = {
device.getDeviceName(),
device.getDeviceType(),
formatDouble(device.getEnergyConsumption()),
formatDouble(device.getEfficiency()),
formatDouble(device.getDeviationRate())
};
for (int j = 0; j < values.length; j++) {
XWPFTableCell cell = row.getCell(j);
XWPFParagraph para = cell.getParagraphs().get(0);
para.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = para.createRun();
run.setText(values[j]);
run.setFontSize(11);
// 根据偏差率设置背景色
if (j == 4) { // 偏差率列
double deviation = device.getDeviationRate() != null ? device.getDeviationRate() : 0;
if (deviation > 10) {
cell.setColor("FFCCCC"); // 红色背景表示高偏差
} else if (deviation > 5) {
cell.setColor("FFE5CC"); // 橙色背景表示中等偏差
}
}
}
}
}
/**
* 添加异常情况分析章节
*/
private void addAnomaliesChapter(XWPFDocument document, EnergyReportDTO report) {
XWPFParagraph chapterPara = document.createParagraph();
chapterPara.setStyle("Heading1");
XWPFRun chapterRun = chapterPara.createRun();
chapterRun.setText("第三章 异常情况分析");
chapterRun.setBold(true);
chapterRun.setFontSize(16);
chapterRun.addBreak();
EnergyReportDTO.Anomaly[] anomalies = report.getAnomalies().toArray(new EnergyReportDTO.Anomaly[0]);
if (anomalies != null && anomalies.length > 0) {
createAnomaliesTable(document, anomalies);
} else {
XWPFParagraph para = document.createParagraph();
para.setIndentationLeft(400);
XWPFRun run = para.createRun();
run.setText("系统运行正常,无异常情况");
run.setFontSize(12);
}
document.createParagraph().createRun().addBreak();
}
/**
* 创建异常情况表格
*/
private void createAnomaliesTable(XWPFDocument document, EnergyReportDTO.Anomaly[] anomalies) {
int rows = anomalies.length + 1;
int cols = 5;
XWPFTable table = document.createTable(rows, cols);
setTableStyle(table);
// 表头
String[] headers = {"设备名称", "异常类型", "异常描述", "严重程度", "预估损失"};
XWPFTableRow headerRow = table.getRow(0);
for (int i = 0; i < headers.length; i++) {
XWPFTableCell cell = headerRow.getCell(i);
cell.setColor("FF6666");
XWPFParagraph para = cell.getParagraphs().get(0);
para.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = para.createRun();
run.setText(headers[i]);
run.setBold(true);
run.setColor("FFFFFF");
run.setFontSize(12);
}
// 数据行
for (int i = 0; i < anomalies.length; i++) {
EnergyReportDTO.Anomaly anomaly = anomalies[i];
XWPFTableRow row = table.getRow(i + 1);
String[] values = {
anomaly.getDeviceName(),
anomaly.getAnomalyType(),
anomaly.getDescription(),
anomaly.getSeverity(),
formatDouble(anomaly.getEstimatedLoss())
};
for (int j = 0; j < values.length; j++) {
XWPFTableCell cell = row.getCell(j);
XWPFParagraph para = cell.getParagraphs().get(0);
para.setAlignment(ParagraphAlignment.LEFT);
XWPFRun run = para.createRun();
run.setText(values[j]);
run.setFontSize(11);
}
}
}
/**
* 添加优化建议章节
*/
private void addSuggestionsChapter(XWPFDocument document, EnergyReportDTO report) {
XWPFParagraph chapterPara = document.createParagraph();
chapterPara.setStyle("Heading1");
XWPFRun chapterRun = chapterPara.createRun();
chapterRun.setText("第四章 优化建议");
chapterRun.setBold(true);
chapterRun.setFontSize(16);
chapterRun.addBreak();
EnergyReportDTO.Suggestion[] suggestions = report.getSuggestions().toArray(new EnergyReportDTO.Suggestion[0]);
if (suggestions != null && suggestions.length > 0) {
for (int i = 0; i < suggestions.length; i++) {
addSuggestionItem(document, suggestions[i], i + 1);
}
} else {
XWPFParagraph para = document.createParagraph();
para.setIndentationLeft(400);
XWPFRun run = para.createRun();
run.setText("暂无优化建议");
run.setFontSize(12);
}
}
/**
* 添加建议项
*/
private void addSuggestionItem(XWPFDocument document, EnergyReportDTO.Suggestion suggestion, int index) {
// 建议标题
XWPFParagraph titlePara = document.createParagraph();
titlePara.setIndentationLeft(400);
XWPFRun titleRun = titlePara.createRun();
titleRun.setText(index + ". " + suggestion.getTitle());
titleRun.setBold(true);
titleRun.setFontSize(13);
titleRun.addBreak();
// 建议详情
String[] details = {
"优先级: " + suggestion.getPriority(),
"预期节能: " + formatDouble(suggestion.getExpectedSaving()) + "%",
"建议操作: " + suggestion.getAction(),
"详细说明: " + suggestion.getDescription()
};
for (String detail : details) {
XWPFParagraph para = document.createParagraph();
para.setIndentationLeft(800);
XWPFRun run = para.createRun();
run.setText("• " + detail);
run.setFontSize(11);
}
document.createParagraph().createRun().addBreak();
}
/**
* 添加专业水印
*/
private void addProfessionalWatermark(XWPFDocument document, String watermarkText) {
try {
// 使用POI的水印API(适用于较新版本)
// 注意:POI 4.1.2对水印支持有限,这里提供兼容性实现
// 方法1:在页眉中添加水印文本
addWatermarkToHeader(document, watermarkText);
} catch (Exception e) {
System.err.println("添加水印时出错: " + e.getMessage());
// 水印失败不影响文档生成
}
}
/**
* 在页眉中添加水印文本
*/
private void addWatermarkToHeader(XWPFDocument document, String watermarkText) {
try {
// 获取或创建页眉
XWPFHeaderFooterPolicy policy = document.getHeaderFooterPolicy();
if (policy == null) {
policy = document.createHeaderFooterPolicy();
}
// 创建页眉
XWPFHeader header = policy.getDefaultHeader();
if (header == null) {
header = policy.createHeader(XWPFHeaderFooterPolicy.DEFAULT);
}
// 添加水印段落
XWPFParagraph watermarkPara = header.createParagraph();
watermarkPara.setAlignment(ParagraphAlignment.CENTER);
XWPFRun watermarkRun = watermarkPara.createRun();
watermarkRun.setText(watermarkText);
watermarkRun.setFontSize(48);
watermarkRun.setColor("D3D3D3");
watermarkRun.setBold(true);
} catch (Exception e) {
System.err.println("添加页眉水印失败: " + e.getMessage());
}
}
/**
* 设置表格样式
*/
private void setTableStyle(XWPFTable table) {
// 设置表格边框
table.setTableAlignment(TableRowAlign.CENTER);
// 设置表格宽度
table.setWidth("80%");
// 设置行高
for (XWPFTableRow row : table.getRows()) {
row.setHeight(300);
}
}
/**
* 格式化双精度数字
*/
private String formatDouble(Double value) {
return value != null ? String.format("%.2f", value) : "-";
}
@Override
public String getFileExtension() {
return ".docx";
}
}

44
mh-system/src/main/java/com/mh/system/service/ai/impl/AsyncReportServiceImpl.java

@ -2,6 +2,7 @@ package com.mh.system.service.ai.impl;
import com.mh.common.config.FileStorageUtil; import com.mh.common.config.FileStorageUtil;
import com.mh.common.core.domain.dto.AsyncTaskDTO; import com.mh.common.core.domain.dto.AsyncTaskDTO;
import com.mh.common.core.domain.dto.AsyncTaskEvent;
import com.mh.common.core.domain.dto.EnergyReportDTO; import com.mh.common.core.domain.dto.EnergyReportDTO;
import com.mh.common.core.domain.dto.ReportRequestDTO; import com.mh.common.core.domain.dto.ReportRequestDTO;
import com.mh.common.utils.uuid.UUID; import com.mh.common.utils.uuid.UUID;
@ -14,6 +15,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -51,8 +53,23 @@ public class AsyncReportServiceImpl {
public SseEmitter registerEmitter(String taskId) { public SseEmitter registerEmitter(String taskId) {
SseEmitter emitter = new SseEmitter(300_000L); SseEmitter emitter = new SseEmitter(300_000L);
emitters.put(taskId, emitter); emitters.put(taskId, emitter);
emitter.onCompletion(() -> emitters.remove(taskId));
emitter.onTimeout(() -> emitters.remove(taskId)); // 发送缓存的事件
List<AsyncTaskEvent> bufferedEvents = taskStore.getAndClearEvents(taskId);
if (bufferedEvents != null) {
for (AsyncTaskEvent event : bufferedEvents) {
sendEventInternal(taskId, emitter, event.getEvent(), event.getData());
}
}
emitter.onCompletion(() -> {
emitters.remove(taskId);
taskStore.removeTask(taskId);
});
emitter.onTimeout(() -> {
emitters.remove(taskId);
taskStore.removeTask(taskId);
});
return emitter; return emitter;
} }
@ -71,9 +88,16 @@ public class AsyncReportServiceImpl {
public void processTask(String taskId, String userMessage) { public void processTask(String taskId, String userMessage) {
try { try {
// 解析自然语言 // 解析自然语言
ReportRequestDTO request = nlpService.parseUserIntent(userMessage); // ReportRequestDTO request = nlpService.parseUserIntent(userMessage);
ReportRequestDTO request = new ReportRequestDTO();
request.setReportType("Energy");
request.setFormat("word");
request.setTimeRange("2026-02-26");
request.setWatermark(true);
request.setWatermarkText("铭汉");
// 获取原始数据(模拟) // 获取原始数据(模拟)
String rawData = fetchRawData(request.getTimeRange()); // String rawData = fetchRawData(request.getTimeRange());
String rawData = "";
// 生成报表并实时推送推理过程 // 生成报表并实时推送推理过程
EnergyReportDTO report = energyReportService.generateReportWithReasoning(rawData, EnergyReportDTO report = energyReportService.generateReportWithReasoning(rawData,
reasoningChunk -> sendEvent(taskId, "reasoning", reasoningChunk)); reasoningChunk -> sendEvent(taskId, "reasoning", reasoningChunk));
@ -94,7 +118,18 @@ public class AsyncReportServiceImpl {
private void sendEvent(String taskId, String event, Object data) { private void sendEvent(String taskId, String event, Object data) {
SseEmitter emitter = emitters.get(taskId); SseEmitter emitter = emitters.get(taskId);
if (emitter != null) { if (emitter != null) {
sendEventInternal(taskId, emitter, event, data);
} else {
// 如果还没有注册 emitter,先缓冲事件
log.info(" emitter 未注册,缓冲事件:{},数据:{}", event, data);
taskStore.bufferEvent(taskId, event, data);
}
}
private void sendEventInternal(String taskId, SseEmitter emitter, String event, Object data) {
try { try {
// 打印输出推送值
log.info("推送事件:{},数据:{}", event, data);
emitter.send(SseEmitter.event().name(event).data(data)); emitter.send(SseEmitter.event().name(event).data(data));
if ("COMPLETED".equals(event) || "FAILED".equals(event)) { if ("COMPLETED".equals(event) || "FAILED".equals(event)) {
emitter.complete(); emitter.complete();
@ -104,7 +139,6 @@ public class AsyncReportServiceImpl {
emitter.completeWithError(e); emitter.completeWithError(e);
} }
} }
}
private String fetchRawData(String timeRange) { private String fetchRawData(String timeRange) {
// 模拟数据,实际应从数据库或IoT平台获取 // 模拟数据,实际应从数据库或IoT平台获取

4
mh-system/src/main/java/com/mh/system/service/ai/impl/EnergyReportServiceImpl.java

@ -101,7 +101,6 @@ public class EnergyReportServiceImpl implements IEnergyReportService {
// } // }
// } // }
// }).blockLast(); // }).blockLast();
log.info("AI原始格式输出:{}", fullResponse); log.info("AI原始格式输出:{}", fullResponse);
// String jsonStr = extractJson(fullResponse.toString()); // String jsonStr = extractJson(fullResponse.toString());
String jsonStr = "{\n" + String jsonStr = "{\n" +
@ -173,6 +172,9 @@ public class EnergyReportServiceImpl implements IEnergyReportService {
" ]\n" + " ]\n" +
"}"; "}";
log.info("AI优化格式输出:{}", jsonStr); log.info("AI优化格式输出:{}", jsonStr);
if (reasoningConsumer != null) {
reasoningConsumer.accept(jsonStr);
}
try { try {
return objectMapper.readValue(jsonStr, EnergyReportDTO.class); return objectMapper.readValue(jsonStr, EnergyReportDTO.class);
} catch (Exception e) { } catch (Exception e) {

23
mh-system/src/main/java/com/mh/system/service/ai/impl/ReportOrchestrationService.java

@ -16,13 +16,16 @@ import java.io.IOException;
*/ */
@Service @Service
public class ReportOrchestrationService { public class ReportOrchestrationService {
private final AdvancedWordGenerationServiceImpl advancedWordService;
private final WordGenerationServiceImpl wordService; private final WordGenerationServiceImpl wordService;
private final ExcelGenerationServiceImpl excelService; private final ExcelGenerationServiceImpl excelService;
private final PdfGenerationServiceImpl pdfService; private final PdfGenerationServiceImpl pdfService;
public ReportOrchestrationService(WordGenerationServiceImpl wordService, public ReportOrchestrationService(AdvancedWordGenerationServiceImpl advancedWordService,
WordGenerationServiceImpl wordService,
ExcelGenerationServiceImpl excelService, ExcelGenerationServiceImpl excelService,
PdfGenerationServiceImpl pdfService) { PdfGenerationServiceImpl pdfService) {
this.advancedWordService = advancedWordService;
this.wordService = wordService; this.wordService = wordService;
this.excelService = excelService; this.excelService = excelService;
this.pdfService = pdfService; this.pdfService = pdfService;
@ -30,7 +33,14 @@ public class ReportOrchestrationService {
public ReportFile generateFile(EnergyReportDTO report, ReportRequestDTO request) throws IOException { public ReportFile generateFile(EnergyReportDTO report, ReportRequestDTO request) throws IOException {
IFileGenerationService generator = switch (request.getFormat().toLowerCase()) { IFileGenerationService generator = switch (request.getFormat().toLowerCase()) {
case "word" -> wordService; case "word" -> {
// 根据请求参数决定使用哪种Word生成服务
if (shouldUseAdvancedWordService(request)) {
yield advancedWordService;
} else {
yield wordService;
}
}
case "excel" -> excelService; case "excel" -> excelService;
default -> pdfService; default -> pdfService;
}; };
@ -39,5 +49,14 @@ public class ReportOrchestrationService {
return new ReportFile(filename, content); return new ReportFile(filename, content);
} }
/**
* 判断是否应该使用高级Word生成服务
*/
private boolean shouldUseAdvancedWordService(ReportRequestDTO request) {
// 可以根据请求参数或其他条件决定
// 比如:需要表格、水印、复杂格式等
return request.getWatermark() != null && request.getWatermark();
}
public record ReportFile(String filename, byte[] content) {} public record ReportFile(String filename, byte[] content) {}
} }

33
mh-system/src/main/java/com/mh/system/service/ai/impl/TaskStoreService.java

@ -1,9 +1,12 @@
package com.mh.system.service.ai.impl; package com.mh.system.service.ai.impl;
import com.mh.common.core.domain.dto.AsyncTaskDTO; import com.mh.common.core.domain.dto.AsyncTaskDTO;
import com.mh.common.core.domain.dto.AsyncTaskEvent;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -18,9 +21,15 @@ import java.util.concurrent.ConcurrentHashMap;
public class TaskStoreService { public class TaskStoreService {
private final Map<String, AsyncTaskDTO> tasks = new ConcurrentHashMap<>(); private final Map<String, AsyncTaskDTO> tasks = new ConcurrentHashMap<>();
private final Map<String, List<AsyncTaskEvent>> eventBuffer = new ConcurrentHashMap<>();
public void save(AsyncTaskDTO task) {
tasks.put(task.getTaskId(), task);
eventBuffer.putIfAbsent(task.getTaskId(), new ArrayList<>());
}
public void save(AsyncTaskDTO task) { tasks.put(task.getTaskId(), task); }
public AsyncTaskDTO get(String taskId) { return tasks.get(taskId); } public AsyncTaskDTO get(String taskId) { return tasks.get(taskId); }
public void updateStatus(String taskId, String status, String message, String downloadUrl) { public void updateStatus(String taskId, String status, String message, String downloadUrl) {
AsyncTaskDTO task = tasks.get(taskId); AsyncTaskDTO task = tasks.get(taskId);
if (task != null) { if (task != null) {
@ -31,4 +40,26 @@ public class TaskStoreService {
} }
} }
public void bufferEvent(String taskId, String event, Object data) {
List<AsyncTaskEvent> events = eventBuffer.get(taskId);
if (events != null) {
events.add(new AsyncTaskEvent(event, data));
}
}
public List<AsyncTaskEvent> getAndClearEvents(String taskId) {
List<AsyncTaskEvent> events = eventBuffer.get(taskId);
if (events != null && !events.isEmpty()) {
List<AsyncTaskEvent> result = new ArrayList<>(events);
events.clear();
return result;
}
return null;
}
public void removeTask(String taskId) {
tasks.remove(taskId);
eventBuffer.remove(taskId);
}
} }

Loading…
Cancel
Save