二级系统联合寻找数据
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package com.klp.da.service;
|
||||
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.da.domain.bo.OeeQueryBo;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OEE 聚合 Service 接口(酸轧线)
|
||||
*
|
||||
* @author klp
|
||||
* @date 2026-01-31
|
||||
*/
|
||||
public interface IDaAcidOeeService {
|
||||
|
||||
/**
|
||||
* 获取 OEE 日汇总(当月走缓存,历史查询走实时)
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 日汇总列表
|
||||
*/
|
||||
List<AcidOeeDailySummaryVo> getDailySummary(OeeQueryBo bo);
|
||||
|
||||
/**
|
||||
* 获取 7 大损失汇总(当月走缓存,历史查询走实时)
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 7 大损失列表
|
||||
*/
|
||||
List<AcidOeeLoss7Vo> getLoss7Summary(OeeQueryBo bo);
|
||||
|
||||
/**
|
||||
* 获取停机事件明细(实时分页查询)
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 分页后的停机事件列表
|
||||
*/
|
||||
TableDataInfo<Klptcm1ProStoppageVo> getStoppageEvents(OeeQueryBo bo);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.klp.da.service;
|
||||
|
||||
import com.klp.common.config.DeepseekConfig;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OEE Word 导出:表格内容大模型分析(可选)
|
||||
*
|
||||
* 默认关闭,通过配置开启:
|
||||
* oee:
|
||||
* word:
|
||||
* ai:
|
||||
* enabled: true
|
||||
*
|
||||
* 大模型连接配置复用 sales.script.ai(DeepSeek):
|
||||
* sales:
|
||||
* script:
|
||||
* ai:
|
||||
* api-key/base-url/model-name/max-retries/temperature
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class OeeWordAiAnalysisService {
|
||||
|
||||
@Value("${oee.word.ai.enabled:false}")
|
||||
private boolean enabled;
|
||||
|
||||
@Qualifier("salesScriptRestTemplate")
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
private final DeepseekConfig deepseekConfig;
|
||||
|
||||
public String analyzeTable(String tableTitle, String tableMarkdown, String extraContext) {
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
if (StringUtils.isBlank(tableMarkdown)) {
|
||||
return null;
|
||||
}
|
||||
if (deepseekConfig == null || StringUtils.isBlank(deepseekConfig.getApiKey())
|
||||
|| StringUtils.isBlank(deepseekConfig.getBaseUrl())
|
||||
|| StringUtils.isBlank(deepseekConfig.getModelName())) {
|
||||
log.warn("OEE Word AI 已开启但 sales.script.ai 配置不完整,跳过分析");
|
||||
return null;
|
||||
}
|
||||
|
||||
String prompt = buildPrompt(tableTitle, tableMarkdown, extraContext);
|
||||
return callAi(prompt);
|
||||
}
|
||||
|
||||
private String buildPrompt(String tableTitle, String tableMarkdown, String extraContext) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("你是一名制造业OEE报表分析专家。请基于下面表格数据,给出简洁的分析结论。\n");
|
||||
sb.append("输出要求:\n");
|
||||
sb.append("1) 用中文输出;\n");
|
||||
sb.append("2) 只输出 3~6 条要点(每条不超过 30 字),不要写长段落;\n");
|
||||
sb.append("3) 可以指出异常/波动/占比最高项/可能原因与建议方向;\n");
|
||||
sb.append("4) 不要复述表格标题,不要输出Markdown表格。\n\n");
|
||||
sb.append("【表格】").append(tableTitle).append("\n");
|
||||
if (StringUtils.isNotBlank(extraContext)) {
|
||||
sb.append("【上下文】").append(extraContext).append("\n");
|
||||
}
|
||||
sb.append("【数据】\n");
|
||||
sb.append(tableMarkdown);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String callAi(String prompt) {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", deepseekConfig.getModelName());
|
||||
|
||||
Map<String, String> systemMessage = new HashMap<>();
|
||||
systemMessage.put("role", "system");
|
||||
systemMessage.put("content", "你是一个严谨的数据分析助手");
|
||||
|
||||
Map<String, String> userMessage = new HashMap<>();
|
||||
userMessage.put("role", "user");
|
||||
userMessage.put("content", prompt);
|
||||
|
||||
requestBody.put("messages", Arrays.asList(systemMessage, userMessage));
|
||||
requestBody.put("temperature", deepseekConfig.getTemperature() == null ? 0.2 : deepseekConfig.getTemperature());
|
||||
requestBody.put("max_tokens", 800);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.setBearerAuth(deepseekConfig.getApiKey());
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
int retries = deepseekConfig.getMaxRetries() == null ? 1 : Math.max(1, deepseekConfig.getMaxRetries());
|
||||
for (int i = 0; i < retries; i++) {
|
||||
try {
|
||||
ResponseEntity<Map> response = restTemplate.postForEntity(
|
||||
deepseekConfig.getBaseUrl() + "/chat/completions", entity, Map.class);
|
||||
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
|
||||
Map<String, Object> body = response.getBody();
|
||||
List<Map<String, Object>> choices = (List<Map<String, Object>>) body.get("choices");
|
||||
if (choices != null && !choices.isEmpty()) {
|
||||
Map<String, Object> choice = choices.get(0);
|
||||
Map<String, Object> message = (Map<String, Object>) choice.get("message");
|
||||
String content = message == null ? null : (String) message.get("content");
|
||||
if (StringUtils.isNotBlank(content)) {
|
||||
return content.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("OEE Word AI 调用失败,重试 {}/{}:{}", i + 1, retries, e.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.klp.da.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.utils.DateUtils;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import com.klp.da.domain.bo.OeeQueryBo;
|
||||
import com.klp.da.service.IDaAcidOeeService;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
|
||||
import com.klp.pocket.acid.service.IAcidOeeService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class DaAcidOeeServiceImpl implements IDaAcidOeeService {
|
||||
|
||||
private static final String SUMMARY_KEY_PATTERN = "oee:report:month:summary:%s:SY";
|
||||
private static final DateTimeFormatter YEAR_MONTH_FMT = DateTimeFormatter.ofPattern("yyyyMM");
|
||||
|
||||
private final IAcidOeeService acidOeeService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public List<AcidOeeDailySummaryVo> getDailySummary(OeeQueryBo bo) {
|
||||
// 检查是否查询当月,如果是,则尝试从 Redis 缓存获取
|
||||
if (isCurrentMonthQuery(bo.getStartDate(), bo.getEndDate())) {
|
||||
String yyyyMM = LocalDate.now().format(YEAR_MONTH_FMT);
|
||||
String summaryKey = String.format(SUMMARY_KEY_PATTERN, yyyyMM);
|
||||
try {
|
||||
String summaryJson = stringRedisTemplate.opsForValue().get(summaryKey);
|
||||
if (StringUtils.isNotBlank(summaryJson)) {
|
||||
log.info("[DaAcidOeeService] Hit cache for acid summary, key={}", summaryKey);
|
||||
return JSON.parseObject(summaryJson, new TypeReference<List<AcidOeeDailySummaryVo>>() {});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[DaAcidOeeService] Failed to get acid summary from redis cache, key={}", summaryKey, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存未命中或查询历史数据,则实时调用 pocket service
|
||||
log.info("[DaAcidOeeService] Cache miss for acid summary, calling pocket service...");
|
||||
return acidOeeService.getDailySummary(
|
||||
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getStartDate())),
|
||||
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getEndDate()))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AcidOeeLoss7Vo> getLoss7Summary(OeeQueryBo bo) {
|
||||
// 7大损失目前没有预计算,直接实时调用
|
||||
return acidOeeService.getLoss7Summary(
|
||||
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getStartDate())),
|
||||
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getEndDate()))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<Klptcm1ProStoppageVo> getStoppageEvents(OeeQueryBo bo) {
|
||||
// 实时分页查询
|
||||
List<Klptcm1ProStoppageVo> list = acidOeeService.getStoppageEvents(
|
||||
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getStartDate())),
|
||||
DateUtils.parseDateToStr("yyyy-MM-dd", DateUtils.toDate(bo.getEndDate()))
|
||||
);
|
||||
return TableDataInfo.build(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断查询范围是否为当月
|
||||
*/
|
||||
private boolean isCurrentMonthQuery(LocalDate startDate, LocalDate endDate) {
|
||||
if (startDate == null || endDate == null) {
|
||||
return false;
|
||||
}
|
||||
LocalDate now = LocalDate.now();
|
||||
LocalDate firstDayOfMonth = now.withDayOfMonth(1);
|
||||
return !startDate.isBefore(firstDayOfMonth) && !endDate.isAfter(now);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user