Compare commits
No commits in common. 'dev_bl' and 'dev' have entirely different histories.
27 changed files with 5 additions and 1830 deletions
@ -1,47 +0,0 @@ |
|||||||
package com.mh.web.controller.ai; |
|
||||||
|
|
||||||
import com.mh.common.core.domain.dto.AsyncTaskDTO; |
|
||||||
import com.mh.system.service.ai.impl.AsyncReportServiceImpl; |
|
||||||
import com.mh.system.service.ai.impl.TaskStoreService; |
|
||||||
import org.springframework.http.MediaType; |
|
||||||
import org.springframework.http.ResponseEntity; |
|
||||||
import org.springframework.web.bind.annotation.*; |
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
|
||||||
|
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 一步报表controller |
|
||||||
* @date 2026-02-27 11:36:57 |
|
||||||
*/ |
|
||||||
@RestController |
|
||||||
@RequestMapping("/api/reports/async") |
|
||||||
public class AsyncReportController { |
|
||||||
private final AsyncReportServiceImpl asyncReportService; |
|
||||||
private final TaskStoreService taskStore; |
|
||||||
|
|
||||||
public AsyncReportController(AsyncReportServiceImpl asyncReportService, TaskStoreService taskStore) { |
|
||||||
this.asyncReportService = asyncReportService; |
|
||||||
this.taskStore = taskStore; |
|
||||||
} |
|
||||||
|
|
||||||
@PostMapping |
|
||||||
public ResponseEntity<Map<String, String>> submitTask(@RequestBody String userMessage) { |
|
||||||
String taskId = asyncReportService.createAsyncTask(userMessage); |
|
||||||
return ResponseEntity.accepted().body(Map.of("taskId", taskId)); |
|
||||||
} |
|
||||||
|
|
||||||
@GetMapping(value = "/{taskId}/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) |
|
||||||
public SseEmitter subscribe(@PathVariable String taskId) { |
|
||||||
return asyncReportService.registerEmitter(taskId); |
|
||||||
} |
|
||||||
|
|
||||||
@GetMapping("/{taskId}") |
|
||||||
public ResponseEntity<AsyncTaskDTO> getTask(@PathVariable String taskId) { |
|
||||||
AsyncTaskDTO task = taskStore.get(taskId); |
|
||||||
return task != null ? ResponseEntity.ok(task) : ResponseEntity.notFound().build(); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -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(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,35 +0,0 @@ |
|||||||
package com.mh.common.config; |
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value; |
|
||||||
import org.springframework.stereotype.Component; |
|
||||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.nio.file.Files; |
|
||||||
import java.nio.file.Path; |
|
||||||
import java.nio.file.Paths; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 文件服务类 |
|
||||||
* @date 2026-02-27 11:32:04 |
|
||||||
*/ |
|
||||||
@Component |
|
||||||
public class FileStorageUtil { |
|
||||||
@Value("${mh.profile:/tmp/energy-reports}") |
|
||||||
private String storageDir; |
|
||||||
|
|
||||||
public String saveFile(String filename, byte[] content) throws IOException { |
|
||||||
Path dir = Paths.get(storageDir); |
|
||||||
if (!Files.exists(dir)) Files.createDirectories(dir); |
|
||||||
Path filePath = dir.resolve(filename); |
|
||||||
Files.write(filePath, content); |
|
||||||
// 生成可下载的URL(假设当前应用提供静态资源访问或下载接口)
|
|
||||||
return ServletUriComponentsBuilder.fromCurrentContextPath() |
|
||||||
.path("/api/files/") |
|
||||||
.path(filename) |
|
||||||
.toUriString(); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,24 +0,0 @@ |
|||||||
package com.mh.common.core.domain.dto; |
|
||||||
|
|
||||||
import lombok.Data; |
|
||||||
|
|
||||||
import java.time.LocalDateTime; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 任务状态dto |
|
||||||
* @date 2026-02-27 10:22:40 |
|
||||||
*/ |
|
||||||
@Data |
|
||||||
public class AsyncTaskDTO { |
|
||||||
|
|
||||||
private String taskId; |
|
||||||
private String status; // PROCESSING, COMPLETED, FAILED
|
|
||||||
private String message; // 成功时的消息或失败原因
|
|
||||||
private String downloadUrl; // 生成文件的下载URL(成功后填充)
|
|
||||||
private LocalDateTime createTime; |
|
||||||
private LocalDateTime completeTime; |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,99 +0,0 @@ |
|||||||
package com.mh.common.core.domain.dto; |
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription; |
|
||||||
import lombok.Builder; |
|
||||||
import lombok.Data; |
|
||||||
|
|
||||||
import java.util.List; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description ai机房能效报表 |
|
||||||
* @date 2026-02-27 10:11:57 |
|
||||||
*/ |
|
||||||
@Data |
|
||||||
@Builder |
|
||||||
public class EnergyReportDTO { |
|
||||||
|
|
||||||
@JsonPropertyDescription("报表生成日期") |
|
||||||
private String reportDate; |
|
||||||
|
|
||||||
@JsonPropertyDescription("整体能效评分 (0-100)") |
|
||||||
private Integer overallScore; |
|
||||||
|
|
||||||
@JsonPropertyDescription("系统综合能效比") |
|
||||||
private Double systemEER; |
|
||||||
|
|
||||||
@JsonPropertyDescription("总能耗 (kWh)") |
|
||||||
private Double totalEnergyConsumption; |
|
||||||
|
|
||||||
@JsonPropertyDescription("各设备能耗明细") |
|
||||||
private List<DeviceEnergy> deviceDetails; |
|
||||||
|
|
||||||
@JsonPropertyDescription("异常情况列表") |
|
||||||
private List<Anomaly> anomalies; |
|
||||||
|
|
||||||
@JsonPropertyDescription("优化建议") |
|
||||||
private List<Suggestion> suggestions; |
|
||||||
|
|
||||||
@Data |
|
||||||
@Builder |
|
||||||
public static class DeviceEnergy { |
|
||||||
@JsonPropertyDescription("设备名称") |
|
||||||
private String deviceName; |
|
||||||
|
|
||||||
@JsonPropertyDescription("设备类型 (冷机/冷冻泵/冷却泵/冷却塔)") |
|
||||||
private String deviceType; |
|
||||||
|
|
||||||
@JsonPropertyDescription("能耗 (kWh)") |
|
||||||
private Double energyConsumption; |
|
||||||
|
|
||||||
@JsonPropertyDescription("运行效率 (%)") |
|
||||||
private Double efficiency; |
|
||||||
|
|
||||||
@JsonPropertyDescription("对比基准的偏差率") |
|
||||||
private Double deviationRate; |
|
||||||
} |
|
||||||
|
|
||||||
@Data |
|
||||||
@Builder |
|
||||||
public static class Anomaly { |
|
||||||
@JsonPropertyDescription("异常设备") |
|
||||||
private String deviceName; |
|
||||||
|
|
||||||
@JsonPropertyDescription("异常类型") |
|
||||||
private String anomalyType; |
|
||||||
|
|
||||||
@JsonPropertyDescription("异常描述") |
|
||||||
private String description; |
|
||||||
|
|
||||||
@JsonPropertyDescription("严重程度 (高/中/低)") |
|
||||||
private String severity; |
|
||||||
|
|
||||||
@JsonPropertyDescription("预估能耗损失 (kWh)") |
|
||||||
private Double estimatedLoss; |
|
||||||
} |
|
||||||
|
|
||||||
@Data |
|
||||||
@Builder |
|
||||||
public static class Suggestion { |
|
||||||
@JsonPropertyDescription("建议标题") |
|
||||||
private String title; |
|
||||||
|
|
||||||
@JsonPropertyDescription("建议详细内容") |
|
||||||
private String description; |
|
||||||
|
|
||||||
@JsonPropertyDescription("预期节能效果 (kWh)") |
|
||||||
private Double expectedSaving; |
|
||||||
|
|
||||||
@JsonPropertyDescription("优先级 (高/中/低)") |
|
||||||
private String priority; |
|
||||||
|
|
||||||
@JsonPropertyDescription("建议执行的操作 (如:检查/维修/调整参数)") |
|
||||||
private String action; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
||||||
@ -1,21 +0,0 @@ |
|||||||
package com.mh.common.core.domain.dto; |
|
||||||
|
|
||||||
import lombok.Data; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 接收前端经过deepseek结构化后的请求 |
|
||||||
* @date 2026-02-27 10:18:04 |
|
||||||
*/ |
|
||||||
@Data |
|
||||||
public class ReportRequestDTO { |
|
||||||
|
|
||||||
private String reportType; // 报表类型,如 "能效报表"
|
|
||||||
private String timeRange; // 时间范围,如 "昨天"、"2026-02-01至2026-02-07"
|
|
||||||
private String format; // 输出格式: word, excel, pdf
|
|
||||||
private Boolean watermark; // 是否需要水印 (默认true)
|
|
||||||
private String watermarkText; // 自定义水印文字,若为空则使用默认
|
|
||||||
|
|
||||||
} |
|
||||||
@ -1,29 +0,0 @@ |
|||||||
package com.mh.framework.config; |
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration; |
|
||||||
import org.springframework.scheduling.annotation.EnableAsync; |
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
|
||||||
|
|
||||||
import java.util.concurrent.Executor; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 自定义线程池 |
|
||||||
* @date 2026-02-27 11:41:52 |
|
||||||
*/ |
|
||||||
@Configuration |
|
||||||
@EnableAsync |
|
||||||
public class AsyncConfig { |
|
||||||
// 可选:自定义线程池
|
|
||||||
public Executor taskExecutor() { |
|
||||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
|
||||||
executor.setCorePoolSize(5); |
|
||||||
executor.setMaxPoolSize(10); |
|
||||||
executor.setQueueCapacity(100); |
|
||||||
executor.setThreadNamePrefix("async-"); |
|
||||||
executor.initialize(); |
|
||||||
return executor; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,17 +0,0 @@ |
|||||||
package com.mh.system.service.ai; |
|
||||||
|
|
||||||
import com.mh.common.core.domain.dto.EnergyReportDTO; |
|
||||||
|
|
||||||
import java.util.function.Consumer; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 流式推理版 |
|
||||||
* @date 2026-02-27 10:36:03 |
|
||||||
*/ |
|
||||||
public interface IEnergyReportService { |
|
||||||
|
|
||||||
public EnergyReportDTO generateReportWithReasoning(String rawData, Consumer<String> reasoningConsumer); |
|
||||||
} |
|
||||||
@ -1,20 +0,0 @@ |
|||||||
package com.mh.system.service.ai; |
|
||||||
|
|
||||||
import com.mh.common.core.domain.dto.EnergyReportDTO; |
|
||||||
import com.mh.common.core.domain.dto.ReportRequestDTO; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 报表生成服务类 |
|
||||||
* @date 2026-02-27 10:50:42 |
|
||||||
*/ |
|
||||||
public interface IFileGenerationService { |
|
||||||
|
|
||||||
byte[] generateReport(EnergyReportDTO report, ReportRequestDTO request) throws IOException; |
|
||||||
String getFileExtension(); |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,15 +0,0 @@ |
|||||||
package com.mh.system.service.ai; |
|
||||||
|
|
||||||
import com.mh.common.core.domain.dto.ReportRequestDTO; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 自然语言解析服务类 |
|
||||||
* @date 2026-02-27 10:25:40 |
|
||||||
*/ |
|
||||||
public interface INLPService { |
|
||||||
|
|
||||||
public ReportRequestDTO parseUserIntent(String userMessage); |
|
||||||
} |
|
||||||
@ -1,114 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
import com.mh.common.config.FileStorageUtil; |
|
||||||
import com.mh.common.core.domain.dto.AsyncTaskDTO; |
|
||||||
import com.mh.common.core.domain.dto.EnergyReportDTO; |
|
||||||
import com.mh.common.core.domain.dto.ReportRequestDTO; |
|
||||||
import com.mh.common.utils.uuid.UUID; |
|
||||||
import com.mh.system.service.ai.IEnergyReportService; |
|
||||||
import com.mh.system.service.ai.INLPService; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springframework.scheduling.annotation.Async; |
|
||||||
import org.springframework.stereotype.Service; |
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
import java.time.LocalDateTime; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 异步任务服务 |
|
||||||
* @date 2026-02-27 11:27:21 |
|
||||||
*/ |
|
||||||
@Service |
|
||||||
@Slf4j |
|
||||||
public class AsyncReportServiceImpl { |
|
||||||
|
|
||||||
private final INLPService nlpService; |
|
||||||
private final IEnergyReportService energyReportService; |
|
||||||
private final ReportOrchestrationService orchestrationService; |
|
||||||
private final TaskStoreService taskStore; |
|
||||||
private final FileStorageUtil fileStorageUtil; |
|
||||||
|
|
||||||
private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>(); |
|
||||||
|
|
||||||
public AsyncReportServiceImpl(INLPService nlpService, |
|
||||||
IEnergyReportService energyReportService, |
|
||||||
ReportOrchestrationService orchestrationService, |
|
||||||
TaskStoreService taskStore, |
|
||||||
FileStorageUtil fileStorageUtil) { |
|
||||||
this.nlpService = nlpService; |
|
||||||
this.energyReportService = energyReportService; |
|
||||||
this.orchestrationService = orchestrationService; |
|
||||||
this.taskStore = taskStore; |
|
||||||
this.fileStorageUtil = fileStorageUtil; |
|
||||||
} |
|
||||||
|
|
||||||
public SseEmitter registerEmitter(String taskId) { |
|
||||||
SseEmitter emitter = new SseEmitter(300_000L); |
|
||||||
emitters.put(taskId, emitter); |
|
||||||
emitter.onCompletion(() -> emitters.remove(taskId)); |
|
||||||
emitter.onTimeout(() -> emitters.remove(taskId)); |
|
||||||
return emitter; |
|
||||||
} |
|
||||||
|
|
||||||
public String createAsyncTask(String userMessage) { |
|
||||||
String taskId = UUID.randomUUID().toString(); |
|
||||||
AsyncTaskDTO task = new AsyncTaskDTO(); |
|
||||||
task.setTaskId(taskId); |
|
||||||
task.setStatus("PROCESSING"); |
|
||||||
task.setCreateTime(LocalDateTime.now()); |
|
||||||
taskStore.save(task); |
|
||||||
processTask(taskId, userMessage); |
|
||||||
return taskId; |
|
||||||
} |
|
||||||
|
|
||||||
@Async |
|
||||||
public void processTask(String taskId, String userMessage) { |
|
||||||
try { |
|
||||||
// 解析自然语言
|
|
||||||
ReportRequestDTO request = nlpService.parseUserIntent(userMessage); |
|
||||||
// 获取原始数据(模拟)
|
|
||||||
String rawData = fetchRawData(request.getTimeRange()); |
|
||||||
// 生成报表并实时推送推理过程
|
|
||||||
EnergyReportDTO report = energyReportService.generateReportWithReasoning(rawData, |
|
||||||
reasoningChunk -> sendEvent(taskId, "reasoning", reasoningChunk)); |
|
||||||
// 生成文件
|
|
||||||
ReportOrchestrationService.ReportFile reportFile = orchestrationService.generateFile(report, request); |
|
||||||
// 保存文件并获取下载URL
|
|
||||||
String downloadUrl = fileStorageUtil.saveFile(reportFile.filename(), reportFile.content()); |
|
||||||
// 更新任务状态
|
|
||||||
taskStore.updateStatus(taskId, "COMPLETED", "报表生成成功", downloadUrl); |
|
||||||
sendEvent(taskId, "COMPLETED", downloadUrl); |
|
||||||
} catch (Exception e) { |
|
||||||
log.error("任务处理失败", e); |
|
||||||
taskStore.updateStatus(taskId, "FAILED", e.getMessage(), null); |
|
||||||
sendEvent(taskId, "FAILED", e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void sendEvent(String taskId, String event, Object data) { |
|
||||||
SseEmitter emitter = emitters.get(taskId); |
|
||||||
if (emitter != null) { |
|
||||||
try { |
|
||||||
emitter.send(SseEmitter.event().name(event).data(data)); |
|
||||||
if ("COMPLETED".equals(event) || "FAILED".equals(event)) { |
|
||||||
emitter.complete(); |
|
||||||
} |
|
||||||
} catch (IOException e) { |
|
||||||
log.error("推送事件失败", e); |
|
||||||
emitter.completeWithError(e); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private String fetchRawData(String timeRange) { |
|
||||||
// 模拟数据,实际应从数据库或IoT平台获取
|
|
||||||
return "{\"date\":\"2026-02-26\",\"outdoorTemp\":32.5,\"coolingLoad\":850}"; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,189 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper; |
|
||||||
import com.mh.common.core.domain.dto.EnergyReportDTO; |
|
||||||
import com.mh.system.service.ai.IEnergyReportService; |
|
||||||
import lombok.extern.slf4j.Slf4j; |
|
||||||
import org.springframework.ai.chat.client.ChatClient; |
|
||||||
import org.springframework.stereotype.Service; |
|
||||||
import reactor.core.publisher.Flux; |
|
||||||
|
|
||||||
import java.time.LocalDate; |
|
||||||
import java.time.format.DateTimeFormatter; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.function.Consumer; |
|
||||||
import java.util.regex.Matcher; |
|
||||||
import java.util.regex.Pattern; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 流式推理版实现类 |
|
||||||
* @date 2026-02-27 10:37:03 |
|
||||||
*/ |
|
||||||
@Slf4j |
|
||||||
@Service |
|
||||||
public class EnergyReportServiceImpl implements IEnergyReportService { |
|
||||||
|
|
||||||
private final ChatClient chatClient; |
|
||||||
private final ObjectMapper objectMapper; |
|
||||||
|
|
||||||
public EnergyReportServiceImpl(ChatClient.Builder builder, ObjectMapper objectMapper) { |
|
||||||
this.chatClient = builder.build(); |
|
||||||
this.objectMapper = objectMapper; |
|
||||||
} |
|
||||||
|
|
||||||
public EnergyReportDTO generateReportWithReasoning(String rawData, Consumer<String> reasoningConsumer) { |
|
||||||
String prompt = """ |
|
||||||
你是一位专业的中央空调系统能效分析专家。 |
|
||||||
原始数据:{rawData} |
|
||||||
分析要求: |
|
||||||
1. 计算系统综合能效比 (EER = 总制冷量 / 总电耗) |
|
||||||
2. 识别能效异常的设备 (效率低于基准值80%视为异常) |
|
||||||
3. 评估各设备能耗占比和运行效率 |
|
||||||
4. 提供具体的优化建议 |
|
||||||
|
|
||||||
请先逐步推理你的分析过程,然后输出最终的 JSON 报表。输出格式如下: |
|
||||||
|
|
||||||
推理过程: |
|
||||||
(分析步骤) |
|
||||||
|
|
||||||
请根据数据生成一份能效分析报表,输出必须为严格的 JSON 格式,包含以下字段: |
|
||||||
- reportDate: 报表生成日期,格式 yyyy-MM-dd |
|
||||||
- overallScore: 整体能效评分 (0-100 的整数) |
|
||||||
- systemEER: 系统综合能效比(数值,保留一位小数) |
|
||||||
- totalEnergyConsumption: 总能耗,单位 kWh |
|
||||||
- deviceDetails: 数组,每个元素包含: |
|
||||||
deviceName: 设备名称 |
|
||||||
deviceType: 设备类型(冷机/冷冻泵/冷却泵/冷却塔) |
|
||||||
energyConsumption: 能耗 (kWh) |
|
||||||
efficiency: 运行效率 (%) |
|
||||||
deviationRate: 对比基准的偏差率 (%) |
|
||||||
- anomalies: 数组,每个元素包含: |
|
||||||
deviceName: 异常设备 |
|
||||||
anomalyType: 异常类型 |
|
||||||
description: 异常描述 |
|
||||||
severity: 严重程度 (高/中/低) |
|
||||||
estimatedLoss: 预估能耗损失 (kWh) |
|
||||||
- suggestions: 数组,每个元素包含: |
|
||||||
title: 建议标题 |
|
||||||
description: 建议详细内容 |
|
||||||
expectedSaving: 预期节能效果 (kWh) |
|
||||||
priority: 优先级 (高/中/低) |
|
||||||
action: 建议执行的操作 |
|
||||||
请先逐步推理你的分析过程,然后输出最终的 JSON 报表。 |
|
||||||
最终报表: |
|
||||||
```json |
|
||||||
<严格按照 EnergyReportDTO JSON 格式输出> |
|
||||||
``` |
|
||||||
今天的日期:{currentDate} |
|
||||||
"""; |
|
||||||
|
|
||||||
Map<String, Object> params = Map.of( |
|
||||||
"rawData", rawData, |
|
||||||
"currentDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE) |
|
||||||
); |
|
||||||
|
|
||||||
StringBuilder fullResponse = new StringBuilder(); |
|
||||||
|
|
||||||
// Flux<String> flux = chatClient.prompt()
|
|
||||||
// .system("你是一个JSON API,但请先详细推理再输出最终JSON。")
|
|
||||||
// .user(userSpec -> userSpec.text(prompt).params(params))
|
|
||||||
// .stream()
|
|
||||||
// .content();
|
|
||||||
//
|
|
||||||
// flux.doOnNext(chunk -> {
|
|
||||||
// fullResponse.append(chunk);
|
|
||||||
// if (!fullResponse.toString().contains("最终报表")) {
|
|
||||||
// if (reasoningConsumer != null) {
|
|
||||||
// reasoningConsumer.accept(chunk);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }).blockLast();
|
|
||||||
|
|
||||||
log.info("AI原始格式输出:{}", fullResponse); |
|
||||||
// String jsonStr = extractJson(fullResponse.toString());
|
|
||||||
String jsonStr = "{\n" + |
|
||||||
" \"reportDate\": \"2026-02-27\",\n" + |
|
||||||
" \"overallScore\": 70,\n" + |
|
||||||
" \"systemEER\": 4.0,\n" + |
|
||||||
" \"totalEnergyConsumption\": 212.5,\n" + |
|
||||||
" \"deviceDetails\": [\n" + |
|
||||||
" {\n" + |
|
||||||
" \"deviceName\": \"冷机-1\",\n" + |
|
||||||
" \"deviceType\": \"冷机\",\n" + |
|
||||||
" \"energyConsumption\": 138.1,\n" + |
|
||||||
" \"efficiency\": 75.0,\n" + |
|
||||||
" \"deviationRate\": -25.0\n" + |
|
||||||
" },\n" + |
|
||||||
" {\n" + |
|
||||||
" \"deviceName\": \"冷冻泵-1\",\n" + |
|
||||||
" \"deviceType\": \"冷冻泵\",\n" + |
|
||||||
" \"energyConsumption\": 31.9,\n" + |
|
||||||
" \"efficiency\": 95.0,\n" + |
|
||||||
" \"deviationRate\": -5.0\n" + |
|
||||||
" },\n" + |
|
||||||
" {\n" + |
|
||||||
" \"deviceName\": \"冷却泵-1\",\n" + |
|
||||||
" \"deviceType\": \"冷却泵\",\n" + |
|
||||||
" \"energyConsumption\": 21.3,\n" + |
|
||||||
" \"efficiency\": 92.0,\n" + |
|
||||||
" \"deviationRate\": -8.0\n" + |
|
||||||
" },\n" + |
|
||||||
" {\n" + |
|
||||||
" \"deviceName\": \"冷却塔-1\",\n" + |
|
||||||
" \"deviceType\": \"冷却塔\",\n" + |
|
||||||
" \"energyConsumption\": 21.3,\n" + |
|
||||||
" \"efficiency\": 88.0,\n" + |
|
||||||
" \"deviationRate\": -12.0\n" + |
|
||||||
" }\n" + |
|
||||||
" ],\n" + |
|
||||||
" \"anomalies\": [\n" + |
|
||||||
" {\n" + |
|
||||||
" \"deviceName\": \"冷机-1\",\n" + |
|
||||||
" \"anomalyType\": \"能效低下\",\n" + |
|
||||||
" \"description\": \"冷机运行效率为75%,低于基准值80%,可能由于换热器污垢或制冷剂不足导致能耗增加。\",\n" + |
|
||||||
" \"severity\": \"高\",\n" + |
|
||||||
" \"estimatedLoss\": 34.5\n" + |
|
||||||
" }\n" + |
|
||||||
" ],\n" + |
|
||||||
" \"suggestions\": [\n" + |
|
||||||
" {\n" + |
|
||||||
" \"title\": \"清洗冷机换热器\",\n" + |
|
||||||
" \"description\": \"检查并清洗冷凝器和蒸发器,清除污垢和沉积物,以提升热交换效率。\",\n" + |
|
||||||
" \"expectedSaving\": 20.0,\n" + |
|
||||||
" \"priority\": \"高\",\n" + |
|
||||||
" \"action\": \"安排专业维护人员进行化学清洗和物理清理。\"\n" + |
|
||||||
" },\n" + |
|
||||||
" {\n" + |
|
||||||
" \"title\": \"优化冷机运行参数\",\n" + |
|
||||||
" \"description\": \"根据室外温度(32.5°C)调整冷机设定温度和运行模式,避免过度冷却。\",\n" + |
|
||||||
" \"expectedSaving\": 10.0,\n" + |
|
||||||
" \"priority\": \"中\",\n" + |
|
||||||
" \"action\": \"调整冷机出水温度设定值至合理范围(如7°C以上),并启用节能模式。\"\n" + |
|
||||||
" },\n" + |
|
||||||
" {\n" + |
|
||||||
" \"title\": \"检查冷冻泵与冷却泵\",\n" + |
|
||||||
" \"description\": \"定期检查水泵轴承和密封状态,确保无异常磨损或泄漏,维持高效运行。\",\n" + |
|
||||||
" \"expectedSaving\": 5.0,\n" + |
|
||||||
" \"priority\": \"低\",\n" + |
|
||||||
" \"action\": \"每月进行一次振动和噪音检测,每年更换一次润滑剂。\"\n" + |
|
||||||
" }\n" + |
|
||||||
" ]\n" + |
|
||||||
"}"; |
|
||||||
log.info("AI优化格式输出:{}", jsonStr); |
|
||||||
try { |
|
||||||
return objectMapper.readValue(jsonStr, EnergyReportDTO.class); |
|
||||||
} catch (Exception e) { |
|
||||||
throw new RuntimeException("解析AI输出失败", e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private String extractJson(String response) { |
|
||||||
Pattern pattern = Pattern.compile("```json\\s*(\\{.*?\\})\\s*```", Pattern.DOTALL); |
|
||||||
Matcher matcher = pattern.matcher(response); |
|
||||||
return matcher.find() ? matcher.group(1) : response; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,204 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
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.apache.poi.hssf.usermodel.*; |
|
||||||
import org.apache.poi.ss.usermodel.*; |
|
||||||
import org.apache.poi.ss.util.CellRangeAddress; |
|
||||||
import org.apache.poi.xssf.streaming.SXSSFSheet; |
|
||||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook; |
|
||||||
import org.apache.poi.xssf.usermodel.*; |
|
||||||
import org.springframework.stereotype.Service; |
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream; |
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description excel添加水印 |
|
||||||
* @date 2026-02-27 10:55:55 |
|
||||||
*/ |
|
||||||
@Service |
|
||||||
public class ExcelGenerationServiceImpl implements IFileGenerationService { |
|
||||||
@Override |
|
||||||
public byte[] generateReport(EnergyReportDTO report, ReportRequestDTO request) throws IOException { |
|
||||||
try (Workbook workbook = new XSSFWorkbook()) { |
|
||||||
Sheet sheet = workbook.createSheet("能效报表"); |
|
||||||
|
|
||||||
// 设置水印(作为背景图片)
|
|
||||||
if (request.getWatermark()) { |
|
||||||
addWatermark(workbook, sheet, request.getWatermarkText()); |
|
||||||
} |
|
||||||
|
|
||||||
// 创建标题行
|
|
||||||
Row titleRow = sheet.createRow(0); |
|
||||||
Cell titleCell = titleRow.createCell(0); |
|
||||||
titleCell.setCellValue("中央空调能效报表"); |
|
||||||
CellStyle titleStyle = workbook.createCellStyle(); |
|
||||||
Font titleFont = workbook.createFont(); |
|
||||||
titleFont.setBold(true); |
|
||||||
titleFont.setFontHeightInPoints((short) 16); |
|
||||||
titleStyle.setFont(titleFont); |
|
||||||
titleCell.setCellStyle(titleStyle); |
|
||||||
|
|
||||||
// 基本信息
|
|
||||||
int rowIdx = 2; |
|
||||||
createCell(sheet, rowIdx++, 0, "报表日期: " + report.getReportDate()); |
|
||||||
createCell(sheet, rowIdx++, 0, "整体能效评分: " + report.getOverallScore()); |
|
||||||
createCell(sheet, rowIdx++, 0, "系统EER: " + report.getSystemEER()); |
|
||||||
createCell(sheet, rowIdx++, 0, "总能耗(kWh): " + report.getTotalEnergyConsumption()); |
|
||||||
|
|
||||||
// 设备明细表头
|
|
||||||
rowIdx++; |
|
||||||
String[] headers = {"设备名称", "设备类型", "能耗(kWh)", "效率(%)", "偏差率(%)"}; |
|
||||||
Row headerRow = sheet.createRow(rowIdx++); |
|
||||||
for (int i = 0; i < headers.length; i++) { |
|
||||||
headerRow.createCell(i).setCellValue(headers[i]); |
|
||||||
} |
|
||||||
|
|
||||||
// 填充设备数据
|
|
||||||
for (EnergyReportDTO.DeviceEnergy device : report.getDeviceDetails()) { |
|
||||||
Row dataRow = sheet.createRow(rowIdx++); |
|
||||||
dataRow.createCell(0).setCellValue(device.getDeviceName()); |
|
||||||
dataRow.createCell(1).setCellValue(device.getDeviceType()); |
|
||||||
dataRow.createCell(2).setCellValue(device.getEnergyConsumption()); |
|
||||||
dataRow.createCell(3).setCellValue(device.getEfficiency()); |
|
||||||
dataRow.createCell(4).setCellValue(device.getDeviationRate()); |
|
||||||
} |
|
||||||
|
|
||||||
// 自动调整列宽
|
|
||||||
for (int i = 0; i < headers.length; i++) { |
|
||||||
sheet.autoSizeColumn(i); |
|
||||||
} |
|
||||||
|
|
||||||
// 异常情况表格
|
|
||||||
rowIdx += 2; |
|
||||||
addSectionTitle(sheet, rowIdx++, "异常情况分析", workbook); |
|
||||||
rowIdx++; |
|
||||||
|
|
||||||
if (report.getAnomalies() != null && !report.getAnomalies().isEmpty()) { |
|
||||||
String[] anomalyHeaders = {"设备名称", "异常类型", "异常描述", "严重程度", "预估损失(kWh)"}; |
|
||||||
Row anomalyHeaderRow = sheet.createRow(rowIdx++); |
|
||||||
for (int i = 0; i < anomalyHeaders.length; i++) { |
|
||||||
anomalyHeaderRow.createCell(i).setCellValue(anomalyHeaders[i]); |
|
||||||
} |
|
||||||
|
|
||||||
for (EnergyReportDTO.Anomaly anomaly : report.getAnomalies()) { |
|
||||||
Row dataRow = sheet.createRow(rowIdx++); |
|
||||||
dataRow.createCell(0).setCellValue(anomaly.getDeviceName()); |
|
||||||
dataRow.createCell(1).setCellValue(anomaly.getAnomalyType()); |
|
||||||
dataRow.createCell(2).setCellValue(anomaly.getDescription()); |
|
||||||
dataRow.createCell(3).setCellValue(anomaly.getSeverity()); |
|
||||||
dataRow.createCell(4).setCellValue(anomaly.getEstimatedLoss() != null ? |
|
||||||
anomaly.getEstimatedLoss() : 0.0); |
|
||||||
} |
|
||||||
|
|
||||||
// 调整异常表格列宽
|
|
||||||
for (int i = 0; i < anomalyHeaders.length; i++) { |
|
||||||
sheet.autoSizeColumn(i); |
|
||||||
} |
|
||||||
} else { |
|
||||||
createCell(sheet, rowIdx++, 0, "无异常情况"); |
|
||||||
} |
|
||||||
|
|
||||||
// 优化建议表格
|
|
||||||
rowIdx += 2; |
|
||||||
addSectionTitle(sheet, rowIdx++, "优化建议", workbook); |
|
||||||
rowIdx++; |
|
||||||
|
|
||||||
if (report.getSuggestions() != null && !report.getSuggestions().isEmpty()) { |
|
||||||
String[] suggestionHeaders = {"优先级", "建议标题", "详细内容", "预期节能(kWh)", "建议操作"}; |
|
||||||
Row suggestionHeaderRow = sheet.createRow(rowIdx++); |
|
||||||
for (int i = 0; i < suggestionHeaders.length; i++) { |
|
||||||
suggestionHeaderRow.createCell(i).setCellValue(suggestionHeaders[i]); |
|
||||||
} |
|
||||||
|
|
||||||
for (EnergyReportDTO.Suggestion suggestion : report.getSuggestions()) { |
|
||||||
Row dataRow = sheet.createRow(rowIdx++); |
|
||||||
dataRow.createCell(0).setCellValue(suggestion.getPriority()); |
|
||||||
dataRow.createCell(1).setCellValue(suggestion.getTitle()); |
|
||||||
dataRow.createCell(2).setCellValue(suggestion.getDescription()); |
|
||||||
dataRow.createCell(3).setCellValue(suggestion.getExpectedSaving() != null ? |
|
||||||
suggestion.getExpectedSaving() : 0.0); |
|
||||||
dataRow.createCell(4).setCellValue(suggestion.getAction()); |
|
||||||
} |
|
||||||
|
|
||||||
// 调整建议表格列宽
|
|
||||||
for (int i = 0; i < suggestionHeaders.length; i++) { |
|
||||||
sheet.autoSizeColumn(i); |
|
||||||
} |
|
||||||
} else { |
|
||||||
createCell(sheet, rowIdx++, 0, "暂无优化建议"); |
|
||||||
} |
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
||||||
workbook.write(baos); |
|
||||||
return baos.toByteArray(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void createCell(Sheet sheet, int row, int col, String value) { |
|
||||||
Row r = sheet.getRow(row); |
|
||||||
if (r == null) r = sheet.createRow(row); |
|
||||||
r.createCell(col).setCellValue(value); |
|
||||||
} |
|
||||||
|
|
||||||
private void addSectionTitle(Sheet sheet, int row, String title, Workbook workbook) { |
|
||||||
Row titleRow = sheet.createRow(row); |
|
||||||
Cell titleCell = titleRow.createCell(0); |
|
||||||
titleCell.setCellValue(title); |
|
||||||
|
|
||||||
CellStyle titleStyle = workbook.createCellStyle(); |
|
||||||
Font titleFont = workbook.createFont(); |
|
||||||
titleFont.setBold(true); |
|
||||||
titleFont.setFontHeightInPoints((short) 12); |
|
||||||
titleStyle.setFont(titleFont); |
|
||||||
titleStyle.setAlignment(HorizontalAlignment.LEFT); |
|
||||||
titleCell.setCellStyle(titleStyle); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Excel水印(使用背景图片方式) |
|
||||||
* 注意:XSSF支持设置背景图片,但水印文字需预先绘制成图片 |
|
||||||
* 简化:此处仅演示思路,实际需要将文字转为BufferedImage再设为背景 |
|
||||||
*/ |
|
||||||
private void addWatermark(Workbook workbook, Sheet sheet, String watermarkText) { |
|
||||||
try { |
|
||||||
// 最简单的水印实现:在工作表顶部添加灰色文本
|
|
||||||
Row watermarkRow = sheet.createRow(0); |
|
||||||
Cell watermarkCell = watermarkRow.createCell(0); |
|
||||||
watermarkCell.setCellValue(watermarkText); |
|
||||||
|
|
||||||
// 创建水印样式
|
|
||||||
CellStyle watermarkStyle = workbook.createCellStyle(); |
|
||||||
Font watermarkFont = workbook.createFont(); |
|
||||||
watermarkFont.setColor(IndexedColors.GREY_25_PERCENT.getIndex()); |
|
||||||
watermarkFont.setFontHeightInPoints((short) 14); |
|
||||||
watermarkFont.setItalic(true); // 斜体效果
|
|
||||||
watermarkStyle.setFont(watermarkFont); |
|
||||||
watermarkStyle.setAlignment(HorizontalAlignment.CENTER); |
|
||||||
|
|
||||||
// 设置背景色为浅灰色(模拟水印效果)
|
|
||||||
watermarkStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); |
|
||||||
watermarkStyle.setFillPattern(FillPatternType.NO_FILL); |
|
||||||
|
|
||||||
watermarkCell.setCellStyle(watermarkStyle); |
|
||||||
|
|
||||||
// 合并单元格让水印覆盖更多区域
|
|
||||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 5)); |
|
||||||
|
|
||||||
} catch (Exception e) { |
|
||||||
// 水印添加失败时不中断主流程
|
|
||||||
System.out.println("水印添加警告: " + e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
@Override |
|
||||||
public String getFileExtension() { |
|
||||||
return ".xlsx"; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,52 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
import com.mh.common.core.domain.dto.ReportRequestDTO; |
|
||||||
import com.mh.system.service.ai.INLPService; |
|
||||||
import org.springframework.ai.chat.client.ChatClient; |
|
||||||
import org.springframework.ai.converter.BeanOutputConverter; |
|
||||||
import org.springframework.stereotype.Service; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 自然语言解析服务实现类 |
|
||||||
* @date 2026-02-27 10:29:30 |
|
||||||
*/ |
|
||||||
@Service |
|
||||||
public class NLPServiceImpl implements INLPService { |
|
||||||
|
|
||||||
private final ChatClient chatClient; |
|
||||||
|
|
||||||
public NLPServiceImpl(ChatClient.Builder builder) { |
|
||||||
this.chatClient = builder.build(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public ReportRequestDTO parseUserIntent(String userMessage) { |
|
||||||
BeanOutputConverter<ReportRequestDTO> converter = new BeanOutputConverter<>(ReportRequestDTO.class); |
|
||||||
String systemPrompt = """ |
|
||||||
你是一个智能助手,负责解析用户关于生成报表的请求。 |
|
||||||
请从用户消息中提取以下信息: |
|
||||||
- reportType: 报表类型 (如 "能效报表"、"故障报表") |
|
||||||
- timeRange: 时间范围 (如 "昨天"、"2026-02-01至2026-02-07") |
|
||||||
- format: 输出格式 (word/excel/pdf) |
|
||||||
- watermark: 是否需要水印 (布尔值) |
|
||||||
- watermarkText: 水印文字 |
|
||||||
|
|
||||||
默认值:format="pdf", watermark=true, watermarkText="内部资料" |
|
||||||
请以严格的JSON格式输出。 |
|
||||||
""" + converter.getFormat(); |
|
||||||
|
|
||||||
ReportRequestDTO request = chatClient.prompt() |
|
||||||
.system(systemPrompt) |
|
||||||
.user(userMessage) |
|
||||||
.call() |
|
||||||
.entity(converter); |
|
||||||
|
|
||||||
if (request.getFormat() == null) request.setFormat("pdf"); |
|
||||||
if (request.getWatermark() == null) request.setWatermark(true); |
|
||||||
if (request.getWatermarkText() == null) request.setWatermarkText("内部资料"); |
|
||||||
return request; |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,287 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
import com.itextpdf.text.*; |
|
||||||
import com.itextpdf.text.pdf.*; |
|
||||||
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.springframework.stereotype.Service; |
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream; |
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description PDF添加水印 |
|
||||||
* @date 2026-02-27 11:06:19 |
|
||||||
*/ |
|
||||||
@Service |
|
||||||
public class PdfGenerationServiceImpl implements IFileGenerationService { |
|
||||||
|
|
||||||
@Override |
|
||||||
public byte[] generateReport(EnergyReportDTO report, ReportRequestDTO request) throws IOException { |
|
||||||
Document document = new Document(PageSize.A4); |
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
||||||
PdfWriter writer = null; |
|
||||||
try { |
|
||||||
writer = PdfWriter.getInstance(document, baos); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
|
|
||||||
// 设置水印事件处理器
|
|
||||||
if (request.getWatermark()) { |
|
||||||
writer.setPageEvent(new WatermarkPageEvent(request.getWatermarkText())); |
|
||||||
} |
|
||||||
|
|
||||||
document.open(); |
|
||||||
|
|
||||||
// 添加标题
|
|
||||||
Font titleFont = new Font(Font.FontFamily.HELVETICA, 20, Font.BOLD); |
|
||||||
Paragraph title = new Paragraph("中央空调能效报表", titleFont); |
|
||||||
title.setAlignment(Element.ALIGN_CENTER); |
|
||||||
title.setSpacingAfter(20); |
|
||||||
try { |
|
||||||
document.add(title); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
|
|
||||||
// 添加基本信息
|
|
||||||
Font normalFont = new Font(Font.FontFamily.HELVETICA, 12); |
|
||||||
try { |
|
||||||
document.add(new Paragraph("报表日期: " + report.getReportDate(), normalFont)); |
|
||||||
document.add(new Paragraph("整体能效评分: " + report.getOverallScore(), normalFont)); |
|
||||||
document.add(new Paragraph("系统EER: " + report.getSystemEER(), normalFont)); |
|
||||||
document.add(new Paragraph("总能耗(kWh): " + report.getTotalEnergyConsumption(), normalFont)); |
|
||||||
document.add(new Paragraph(" ")); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
|
|
||||||
// 创建表格
|
|
||||||
PdfPTable table = new PdfPTable(5); |
|
||||||
table.setWidthPercentage(100); |
|
||||||
table.setSpacingBefore(10f); |
|
||||||
table.setSpacingAfter(10f); |
|
||||||
|
|
||||||
// 设置列宽
|
|
||||||
float[] columnWidths = {2f, 2f, 2f, 2f, 2f}; |
|
||||||
try { |
|
||||||
table.setWidths(columnWidths); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
|
|
||||||
// 添加表头
|
|
||||||
Font headerFont = new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD); |
|
||||||
addTableHeader(table, "设备名称", headerFont); |
|
||||||
addTableHeader(table, "设备类型", headerFont); |
|
||||||
addTableHeader(table, "能耗(kWh)", headerFont); |
|
||||||
addTableHeader(table, "效率(%)", headerFont); |
|
||||||
addTableHeader(table, "偏差率(%)", headerFont); |
|
||||||
|
|
||||||
// 添加数据行
|
|
||||||
Font dataFont = new Font(Font.FontFamily.HELVETICA, 10); |
|
||||||
for (EnergyReportDTO.DeviceEnergy device : report.getDeviceDetails()) { |
|
||||||
addTableCell(table, device.getDeviceName(), dataFont); |
|
||||||
addTableCell(table, device.getDeviceType(), dataFont); |
|
||||||
addTableCell(table, String.valueOf(device.getEnergyConsumption()), dataFont); |
|
||||||
addTableCell(table, String.valueOf(device.getEfficiency()), dataFont); |
|
||||||
addTableCell(table, String.valueOf(device.getDeviationRate()), dataFont); |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
document.add(table); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
|
|
||||||
// 异常情况分析
|
|
||||||
try { |
|
||||||
addSectionTitle(document, "异常情况分析", headerFont); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
if (report.getAnomalies() != null && !report.getAnomalies().isEmpty()) { |
|
||||||
PdfPTable anomalyTable = new PdfPTable(5); |
|
||||||
anomalyTable.setWidthPercentage(100); |
|
||||||
anomalyTable.setSpacingBefore(10f); |
|
||||||
anomalyTable.setSpacingAfter(10f); |
|
||||||
|
|
||||||
float[] anomalyColumnWidths = {2f, 2f, 3f, 1.5f, 2f}; |
|
||||||
try { |
|
||||||
anomalyTable.setWidths(anomalyColumnWidths); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
|
|
||||||
addTableHeader(anomalyTable, "设备名称", headerFont); |
|
||||||
addTableHeader(anomalyTable, "异常类型", headerFont); |
|
||||||
addTableHeader(anomalyTable, "异常描述", headerFont); |
|
||||||
addTableHeader(anomalyTable, "严重程度", headerFont); |
|
||||||
addTableHeader(anomalyTable, "预估损失(kWh)", headerFont); |
|
||||||
|
|
||||||
for (EnergyReportDTO.Anomaly anomaly : report.getAnomalies()) { |
|
||||||
addTableCell(anomalyTable, anomaly.getDeviceName(), dataFont); |
|
||||||
addTableCell(anomalyTable, anomaly.getAnomalyType(), dataFont); |
|
||||||
addTableCell(anomalyTable, anomaly.getDescription(), dataFont); |
|
||||||
addTableCell(anomalyTable, anomaly.getSeverity(), dataFont); |
|
||||||
addTableCell(anomalyTable, anomaly.getEstimatedLoss() != null ? |
|
||||||
String.format("%.2f", anomaly.getEstimatedLoss()) : "-", dataFont); |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
document.add(anomalyTable); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
} else { |
|
||||||
try { |
|
||||||
document.add(new Paragraph("无异常情况", normalFont)); |
|
||||||
document.add(new Paragraph(" ")); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 优化建议
|
|
||||||
try { |
|
||||||
addSectionTitle(document, "优化建议", headerFont); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
if (report.getSuggestions() != null && !report.getSuggestions().isEmpty()) { |
|
||||||
PdfPTable suggestionTable = new PdfPTable(5); |
|
||||||
suggestionTable.setWidthPercentage(100); |
|
||||||
suggestionTable.setSpacingBefore(10f); |
|
||||||
suggestionTable.setSpacingAfter(10f); |
|
||||||
|
|
||||||
float[] suggestionColumnWidths = {1.5f, 2f, 3f, 2f, 2f}; |
|
||||||
try { |
|
||||||
suggestionTable.setWidths(suggestionColumnWidths); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
|
|
||||||
addTableHeader(suggestionTable, "优先级", headerFont); |
|
||||||
addTableHeader(suggestionTable, "建议标题", headerFont); |
|
||||||
addTableHeader(suggestionTable, "详细内容", headerFont); |
|
||||||
addTableHeader(suggestionTable, "预期节能(kWh)", headerFont); |
|
||||||
addTableHeader(suggestionTable, "建议操作", headerFont); |
|
||||||
|
|
||||||
for (EnergyReportDTO.Suggestion suggestion : report.getSuggestions()) { |
|
||||||
addTableCell(suggestionTable, suggestion.getPriority(), dataFont); |
|
||||||
addTableCell(suggestionTable, suggestion.getTitle(), dataFont); |
|
||||||
addTableCell(suggestionTable, suggestion.getDescription(), dataFont); |
|
||||||
addTableCell(suggestionTable, suggestion.getExpectedSaving() != null ? |
|
||||||
String.format("%.2f", suggestion.getExpectedSaving()) : "-", dataFont); |
|
||||||
addTableCell(suggestionTable, suggestion.getAction(), dataFont); |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
document.add(suggestionTable); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
} else { |
|
||||||
try { |
|
||||||
document.add(new Paragraph("暂无优化建议", normalFont)); |
|
||||||
} catch (DocumentException e) { |
|
||||||
throw new RuntimeException(e); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
document.close(); |
|
||||||
|
|
||||||
return baos.toByteArray(); |
|
||||||
} |
|
||||||
|
|
||||||
private void addTableHeader(PdfPTable table, String headerText, Font font) { |
|
||||||
PdfPCell header = new PdfPCell(new Phrase(headerText, font)); |
|
||||||
header.setHorizontalAlignment(Element.ALIGN_CENTER); |
|
||||||
header.setVerticalAlignment(Element.ALIGN_MIDDLE); |
|
||||||
header.setBackgroundColor(BaseColor.LIGHT_GRAY); |
|
||||||
table.addCell(header); |
|
||||||
} |
|
||||||
|
|
||||||
private void addTableCell(PdfPTable table, String text, Font font) { |
|
||||||
PdfPCell cell = new PdfPCell(new Phrase(text, font)); |
|
||||||
cell.setHorizontalAlignment(Element.ALIGN_CENTER); |
|
||||||
cell.setVerticalAlignment(Element.ALIGN_MIDDLE); |
|
||||||
table.addCell(cell); |
|
||||||
} |
|
||||||
|
|
||||||
private void addSectionTitle(Document document, String title, Font font) throws DocumentException { |
|
||||||
Paragraph sectionTitle = new Paragraph(title, font); |
|
||||||
sectionTitle.setAlignment(Element.ALIGN_LEFT); |
|
||||||
sectionTitle.setSpacingBefore(20f); |
|
||||||
sectionTitle.setSpacingAfter(10f); |
|
||||||
document.add(sectionTitle); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String getFileExtension() { |
|
||||||
return ".pdf"; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* PDF水印页面事件处理器 |
|
||||||
*/ |
|
||||||
private static class WatermarkPageEvent extends PdfPageEventHelper { |
|
||||||
private final String watermarkText; |
|
||||||
private final Font watermarkFont; |
|
||||||
|
|
||||||
public WatermarkPageEvent(String watermarkText) { |
|
||||||
this.watermarkText = watermarkText; |
|
||||||
// 创建水印字体
|
|
||||||
this.watermarkFont = new Font(Font.FontFamily.HELVETICA, 50, Font.NORMAL, BaseColor.LIGHT_GRAY); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void onEndPage(PdfWriter writer, Document document) { |
|
||||||
try { |
|
||||||
PdfContentByte canvas = writer.getDirectContentUnder(); |
|
||||||
Rectangle pageSize = document.getPageSize(); |
|
||||||
|
|
||||||
// 保存当前图形状态
|
|
||||||
canvas.saveState(); |
|
||||||
|
|
||||||
// 设置透明度
|
|
||||||
PdfGState gState = new PdfGState(); |
|
||||||
gState.setFillOpacity(0.2f); // 20%不透明度
|
|
||||||
canvas.setGState(gState); |
|
||||||
|
|
||||||
// 设置字体和颜色
|
|
||||||
canvas.beginText(); |
|
||||||
canvas.setFontAndSize(watermarkFont.getBaseFont(), 50); |
|
||||||
canvas.setColorFill(BaseColor.LIGHT_GRAY); |
|
||||||
|
|
||||||
// 计算水印位置(页面中心)
|
|
||||||
float x = (pageSize.getLeft() + pageSize.getRight()) / 2; |
|
||||||
float y = (pageSize.getTop() + pageSize.getBottom()) / 2; |
|
||||||
|
|
||||||
// 设置文字矩阵并旋转45度
|
|
||||||
canvas.setTextMatrix( |
|
||||||
(float) Math.cos(Math.toRadians(45)), |
|
||||||
(float) Math.sin(Math.toRadians(45)), |
|
||||||
(float) -Math.sin(Math.toRadians(45)), |
|
||||||
(float) Math.cos(Math.toRadians(45)), |
|
||||||
x, y |
|
||||||
); |
|
||||||
|
|
||||||
// 显示水印文字
|
|
||||||
canvas.showTextAligned(Element.ALIGN_CENTER, watermarkText, 0, 0, 0); |
|
||||||
canvas.endText(); |
|
||||||
|
|
||||||
// 恢复图形状态
|
|
||||||
canvas.restoreState(); |
|
||||||
|
|
||||||
} catch (Exception e) { |
|
||||||
System.err.println("添加PDF水印失败: " + e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,43 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
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.springframework.stereotype.Service; |
|
||||||
|
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 文件生成服务 |
|
||||||
* @date 2026-02-27 11:24:45 |
|
||||||
*/ |
|
||||||
@Service |
|
||||||
public class ReportOrchestrationService { |
|
||||||
private final WordGenerationServiceImpl wordService; |
|
||||||
private final ExcelGenerationServiceImpl excelService; |
|
||||||
private final PdfGenerationServiceImpl pdfService; |
|
||||||
|
|
||||||
public ReportOrchestrationService(WordGenerationServiceImpl wordService, |
|
||||||
ExcelGenerationServiceImpl excelService, |
|
||||||
PdfGenerationServiceImpl pdfService) { |
|
||||||
this.wordService = wordService; |
|
||||||
this.excelService = excelService; |
|
||||||
this.pdfService = pdfService; |
|
||||||
} |
|
||||||
|
|
||||||
public ReportFile generateFile(EnergyReportDTO report, ReportRequestDTO request) throws IOException { |
|
||||||
IFileGenerationService generator = switch (request.getFormat().toLowerCase()) { |
|
||||||
case "word" -> wordService; |
|
||||||
case "excel" -> excelService; |
|
||||||
default -> pdfService; |
|
||||||
}; |
|
||||||
byte[] content = generator.generateReport(report, request); |
|
||||||
String filename = "能效报表_" + report.getReportDate() + generator.getFileExtension(); |
|
||||||
return new ReportFile(filename, content); |
|
||||||
} |
|
||||||
|
|
||||||
public record ReportFile(String filename, byte[] content) {} |
|
||||||
} |
|
||||||
@ -1,34 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
import com.mh.common.core.domain.dto.AsyncTaskDTO; |
|
||||||
import org.springframework.stereotype.Component; |
|
||||||
|
|
||||||
import java.time.LocalDateTime; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description 任务存储服务类 |
|
||||||
* @date 2026-02-27 11:30:06 |
|
||||||
*/ |
|
||||||
@Component |
|
||||||
public class TaskStoreService { |
|
||||||
|
|
||||||
private final Map<String, AsyncTaskDTO> tasks = new ConcurrentHashMap<>(); |
|
||||||
|
|
||||||
public void save(AsyncTaskDTO task) { tasks.put(task.getTaskId(), task); } |
|
||||||
public AsyncTaskDTO get(String taskId) { return tasks.get(taskId); } |
|
||||||
public void updateStatus(String taskId, String status, String message, String downloadUrl) { |
|
||||||
AsyncTaskDTO task = tasks.get(taskId); |
|
||||||
if (task != null) { |
|
||||||
task.setStatus(status); |
|
||||||
task.setMessage(message); |
|
||||||
task.setDownloadUrl(downloadUrl); |
|
||||||
task.setCompleteTime(LocalDateTime.now()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -1,328 +0,0 @@ |
|||||||
package com.mh.system.service.ai.impl; |
|
||||||
|
|
||||||
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.springframework.stereotype.Service; |
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream; |
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author LJF |
|
||||||
* @version 1.0 |
|
||||||
* @project EEMCS |
|
||||||
* @description Word文档生成服务 |
|
||||||
* @date 2026-02-27 10:51:51 |
|
||||||
*/ |
|
||||||
@Service |
|
||||||
public class WordGenerationServiceImpl implements IFileGenerationService { |
|
||||||
|
|
||||||
@Override |
|
||||||
public byte[] generateReport(EnergyReportDTO report, ReportRequestDTO request) throws IOException { |
|
||||||
try (XWPFDocument document = new XWPFDocument()) { |
|
||||||
// 添加水印
|
|
||||||
if (request.getWatermark()) { |
|
||||||
addWatermark(document, request.getWatermarkText()); |
|
||||||
} |
|
||||||
|
|
||||||
// 创建标题
|
|
||||||
addTitle(document, "中央空调能效报表"); |
|
||||||
|
|
||||||
// 添加基本信息
|
|
||||||
addBasicInfo(document, report); |
|
||||||
|
|
||||||
// 设备明细表格
|
|
||||||
addDeviceDetailsTable(document, report.getDeviceDetails().toArray(new EnergyReportDTO.DeviceEnergy[0])); |
|
||||||
|
|
||||||
// 异常情况分析
|
|
||||||
addAnomaliesSection(document, report.getAnomalies().toArray(new EnergyReportDTO.Anomaly[0])); |
|
||||||
|
|
||||||
// 优化建议
|
|
||||||
addSuggestionsSection(document, report.getSuggestions().toArray(new EnergyReportDTO.Suggestion[0])); |
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
|
||||||
document.write(baos); |
|
||||||
return baos.toByteArray(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void addTitle(XWPFDocument document, String title) { |
|
||||||
XWPFParagraph para = document.createParagraph(); |
|
||||||
para.setAlignment(ParagraphAlignment.CENTER); |
|
||||||
XWPFRun run = para.createRun(); |
|
||||||
run.setText(title); |
|
||||||
// 使用反射安全设置样式
|
|
||||||
setRunBold(run, true); |
|
||||||
setRunFontSize(run, 20); |
|
||||||
} |
|
||||||
|
|
||||||
private void addBasicInfo(XWPFDocument document, EnergyReportDTO report) { |
|
||||||
XWPFParagraph para = document.createParagraph(); |
|
||||||
XWPFRun run = para.createRun(); |
|
||||||
run.setText("报表日期: " + report.getReportDate()); |
|
||||||
setRunFontSize(run, 12); |
|
||||||
|
|
||||||
para = document.createParagraph(); |
|
||||||
run = para.createRun(); |
|
||||||
run.setText("整体能效评分: " + report.getOverallScore()); |
|
||||||
setRunFontSize(run, 12); |
|
||||||
|
|
||||||
para = document.createParagraph(); |
|
||||||
run = para.createRun(); |
|
||||||
run.setText("系统综合能效比(EER): " + report.getSystemEER()); |
|
||||||
setRunFontSize(run, 12); |
|
||||||
|
|
||||||
para = document.createParagraph(); |
|
||||||
run = para.createRun(); |
|
||||||
run.setText("总能耗: " + formatDouble(report.getTotalEnergyConsumption()) + " kWh"); |
|
||||||
setRunFontSize(run, 12); |
|
||||||
|
|
||||||
para = document.createParagraph(); |
|
||||||
run = para.createRun(); |
|
||||||
run.setText(" "); |
|
||||||
setRunFontSize(run, 8); |
|
||||||
} |
|
||||||
|
|
||||||
private void addDeviceDetailsTable(XWPFDocument document, EnergyReportDTO.DeviceEnergy[] devices) { |
|
||||||
if (devices == null || devices.length == 0) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// 使用纯文本方式替代表格,完全避免POI表格API
|
|
||||||
addSectionTitle(document, "设备明细"); |
|
||||||
|
|
||||||
// 添加表头
|
|
||||||
XWPFParagraph headerPara = document.createParagraph(); |
|
||||||
XWPFRun headerRun = headerPara.createRun(); |
|
||||||
headerRun.setText("设备名称\t设备类型\t能耗\t效率(%)\t偏差率(%)"); |
|
||||||
setRunBold(headerRun, true); |
|
||||||
|
|
||||||
// 添加数据行
|
|
||||||
for (EnergyReportDTO.DeviceEnergy device : devices) { |
|
||||||
XWPFParagraph dataPara = document.createParagraph(); |
|
||||||
XWPFRun dataRun = dataPara.createRun(); |
|
||||||
String rowData = String.format("%s\t%s\t%s\t%s\t%s", |
|
||||||
device.getDeviceName(), |
|
||||||
device.getDeviceType(), |
|
||||||
formatDouble(device.getEnergyConsumption()), |
|
||||||
formatDouble(device.getEfficiency()), |
|
||||||
formatDouble(device.getDeviationRate())); |
|
||||||
dataRun.setText(rowData); |
|
||||||
} |
|
||||||
|
|
||||||
document.createParagraph(); |
|
||||||
} |
|
||||||
|
|
||||||
private void addAnomaliesSection(XWPFDocument document, EnergyReportDTO.Anomaly[] anomalies) { |
|
||||||
addSectionTitle(document, "异常情况分析"); |
|
||||||
|
|
||||||
if (anomalies != null && anomalies.length > 0) { |
|
||||||
// 使用列表方式替代表格
|
|
||||||
XWPFParagraph headerPara = document.createParagraph(); |
|
||||||
XWPFRun headerRun = headerPara.createRun(); |
|
||||||
headerRun.setText("设备名称 | 异常类型 | 异常描述 | 严重程度 | 预估损失"); |
|
||||||
setRunBold(headerRun, true); |
|
||||||
|
|
||||||
for (EnergyReportDTO.Anomaly anomaly : anomalies) { |
|
||||||
XWPFParagraph itemPara = document.createParagraph(); |
|
||||||
XWPFRun itemRun = itemPara.createRun(); |
|
||||||
String itemText = String.format("%s | %s | %s | %s | %s", |
|
||||||
anomaly.getDeviceName(), |
|
||||||
anomaly.getAnomalyType(), |
|
||||||
anomaly.getDescription(), |
|
||||||
anomaly.getSeverity(), |
|
||||||
formatDouble(anomaly.getEstimatedLoss())); |
|
||||||
itemRun.setText(itemText); |
|
||||||
} |
|
||||||
} else { |
|
||||||
XWPFParagraph para = document.createParagraph(); |
|
||||||
XWPFRun run = para.createRun(); |
|
||||||
run.setText("无异常情况"); |
|
||||||
setRunFontSize(run, 12); |
|
||||||
} |
|
||||||
|
|
||||||
document.createParagraph(); |
|
||||||
} |
|
||||||
|
|
||||||
private void addSuggestionsSection(XWPFDocument document, EnergyReportDTO.Suggestion[] suggestions) { |
|
||||||
addSectionTitle(document, "优化建议"); |
|
||||||
|
|
||||||
if (suggestions != null && suggestions.length > 0) { |
|
||||||
// 使用编号列表方式展示建议
|
|
||||||
for (int i = 0; i < suggestions.length; i++) { |
|
||||||
EnergyReportDTO.Suggestion suggestion = suggestions[i]; |
|
||||||
|
|
||||||
// 添加建议标题
|
|
||||||
XWPFParagraph titlePara = document.createParagraph(); |
|
||||||
XWPFRun titleRun = titlePara.createRun(); |
|
||||||
titleRun.setText((i + 1) + ". " + suggestion.getTitle()); |
|
||||||
setRunBold(titleRun, true); |
|
||||||
|
|
||||||
// 添加详细信息
|
|
||||||
XWPFParagraph detailPara = document.createParagraph(); |
|
||||||
XWPFRun detailRun = detailPara.createRun(); |
|
||||||
String detailText = String.format("优先级: %s | 预期节能: %s | 建议操作: %s\n详细内容: %s", |
|
||||||
suggestion.getPriority(), |
|
||||||
formatDouble(suggestion.getExpectedSaving()), |
|
||||||
suggestion.getAction(), |
|
||||||
suggestion.getDescription()); |
|
||||||
detailRun.setText(detailText); |
|
||||||
setRunFontSize(detailRun, 11); |
|
||||||
} |
|
||||||
} else { |
|
||||||
XWPFParagraph para = document.createParagraph(); |
|
||||||
XWPFRun run = para.createRun(); |
|
||||||
run.setText("暂无优化建议"); |
|
||||||
setRunFontSize(run, 12); |
|
||||||
} |
|
||||||
|
|
||||||
document.createParagraph(); |
|
||||||
} |
|
||||||
|
|
||||||
private void addSectionTitle(XWPFDocument document, String title) { |
|
||||||
XWPFParagraph para = document.createParagraph(); |
|
||||||
setParagraphSpacing(para, 200, 100); |
|
||||||
XWPFRun run = para.createRun(); |
|
||||||
run.setText(title); |
|
||||||
setRunBold(run, true); |
|
||||||
setRunFontSize(run, 14); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 安全设置段落间距 |
|
||||||
*/ |
|
||||||
private void setParagraphSpacing(XWPFParagraph para, int before, int after) { |
|
||||||
try { |
|
||||||
para.setSpacingBefore(before); |
|
||||||
para.setSpacingAfter(after); |
|
||||||
} catch (NoSuchMethodError | NoClassDefFoundError e) { |
|
||||||
System.err.println("设置段落间距失败,跳过: " + e.getMessage()); |
|
||||||
} catch (Exception e) { |
|
||||||
// 静默处理
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private void addWatermark(XWPFDocument document, String watermarkText) { |
|
||||||
try { |
|
||||||
// 使用完全兼容POI 4.1.2的水印实现
|
|
||||||
addCompatibleWatermark(document, watermarkText); |
|
||||||
} catch (Exception e) { |
|
||||||
// 水印添加失败时静默处理,不影响文档生成
|
|
||||||
System.err.println("添加水印失败: " + e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 完全兼容POI 4.1.2的水印实现 |
|
||||||
*/ |
|
||||||
private void addCompatibleWatermark(XWPFDocument doc, String watermarkText) { |
|
||||||
try { |
|
||||||
// 在文档开头添加水印文本作为替代方案
|
|
||||||
XWPFParagraph watermarkPara = doc.createParagraph(); |
|
||||||
watermarkPara.setAlignment(ParagraphAlignment.CENTER); |
|
||||||
|
|
||||||
XWPFRun watermarkRun = watermarkPara.createRun(); |
|
||||||
watermarkRun.setText(watermarkText); |
|
||||||
|
|
||||||
// 使用兼容的方法设置样式
|
|
||||||
setRunFontSize(watermarkRun, 24); |
|
||||||
setRunColor(watermarkRun, "CCCCCC"); |
|
||||||
|
|
||||||
// 添加间距
|
|
||||||
setParagraphSpacing(watermarkPara, 100, 100); |
|
||||||
|
|
||||||
} catch (Exception e) { |
|
||||||
System.err.println("兼容水印添加失败: " + e.getMessage()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 安全设置字体大小(使用 try-catch 避免版本兼容性问题) |
|
||||||
*/ |
|
||||||
private void setRunFontSize(XWPFRun run, int size) { |
|
||||||
try { |
|
||||||
run.setFontSize(size); |
|
||||||
} catch (NoSuchMethodError | NoClassDefFoundError e) { |
|
||||||
System.err.println("设置字体大小失败,跳过: " + e.getMessage()); |
|
||||||
} catch (Exception e) { |
|
||||||
// 静默处理
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 安全设置粗体 |
|
||||||
*/ |
|
||||||
private void setRunBold(XWPFRun run, boolean bold) { |
|
||||||
try { |
|
||||||
run.setBold(bold); |
|
||||||
} catch (NoSuchMethodError | NoClassDefFoundError e) { |
|
||||||
System.err.println("设置粗体失败,跳过: " + e.getMessage()); |
|
||||||
} catch (Exception e) { |
|
||||||
// 静默处理
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 安全设置斜体 |
|
||||||
*/ |
|
||||||
private void setRunItalic(XWPFRun run, boolean italic) { |
|
||||||
try { |
|
||||||
run.setItalic(italic); |
|
||||||
} catch (NoSuchMethodError | NoClassDefFoundError e) { |
|
||||||
System.err.println("设置斜体失败,跳过: " + e.getMessage()); |
|
||||||
} catch (Exception e) { |
|
||||||
// 静默处理
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 安全设置字体 |
|
||||||
*/ |
|
||||||
private void setRunFontFamily(XWPFRun run, String fontFamily) { |
|
||||||
try { |
|
||||||
run.setFontFamily(fontFamily); |
|
||||||
} catch (NoSuchMethodError | NoClassDefFoundError e) { |
|
||||||
System.err.println("设置字体失败,跳过: " + e.getMessage()); |
|
||||||
} catch (Exception e) { |
|
||||||
// 静默处理
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 安全设置颜色 |
|
||||||
*/ |
|
||||||
private void setRunColor(XWPFRun run, String color) { |
|
||||||
try { |
|
||||||
run.setColor(color); |
|
||||||
} catch (NoSuchMethodError | NoClassDefFoundError e) { |
|
||||||
System.err.println("设置颜色失败,跳过: " + e.getMessage()); |
|
||||||
} catch (Exception e) { |
|
||||||
// 静默处理
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* 安全的段落创建方法 |
|
||||||
*/ |
|
||||||
private XWPFParagraph createSafeParagraph(XWPFDocument document) { |
|
||||||
try { |
|
||||||
return document.createParagraph(); |
|
||||||
} catch (Exception e) { |
|
||||||
System.err.println("创建段落失败: " + e.getMessage()); |
|
||||||
return null; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private String formatDouble(Double value) { |
|
||||||
return value != null ? String.format("%.2f", value) : "-"; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public String getFileExtension() { |
|
||||||
return ".docx"; |
|
||||||
} |
|
||||||
} |
|
||||||
Loading…
Reference in new issue