二级系统联合寻找数据
This commit is contained in:
@@ -5,14 +5,23 @@ import com.klp.common.core.controller.BaseController;
|
||||
import com.klp.common.core.domain.R;
|
||||
import com.klp.common.core.page.TableDataInfo;
|
||||
import com.klp.common.utils.StringUtils;
|
||||
import com.klp.da.service.OeeWordAiAnalysisService;
|
||||
import com.klp.da.service.OeeReportJobService;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeRegressionVo;
|
||||
import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo;
|
||||
import com.klp.pocket.acid.service.IAcidOeeService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFRun;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFTable;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -23,11 +32,17 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -48,6 +63,7 @@ public class OeeReportController extends BaseController {
|
||||
private final IAcidOeeService acidOeeService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final OeeReportJobService oeeReportJobService;
|
||||
private final OeeWordAiAnalysisService oeeWordAiAnalysisService;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
@@ -177,21 +193,233 @@ public class OeeReportController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 酸轧线理论节拍回归数据
|
||||
* 酸轧线理论节拍(统计口径:历史优良日中位数)
|
||||
*
|
||||
* 路由:GET /oee/line/acid/regression
|
||||
* 说明:
|
||||
* - {@code startDate} / {@code endDate} 可选,若为空则由 pocket 按“近6个月”默认处理。
|
||||
* 路由:GET /oee/line/acid/idealCycle
|
||||
*/
|
||||
@GetMapping("/acid/regression")
|
||||
public R<AcidOeeRegressionVo> getAcidRegression(
|
||||
@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate
|
||||
@GetMapping("/acid/idealCycle")
|
||||
public R<AcidOeeIdealCycleVo> getAcidIdealCycle(
|
||||
@RequestParam String startDate,
|
||||
@RequestParam String endDate
|
||||
) {
|
||||
AcidOeeRegressionVo data = acidOeeService.getRegressionData(startDate, endDate);
|
||||
AcidOeeIdealCycleVo data = acidOeeService.getIdealCycle(startDate, endDate);
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出酸轧线 OEE 报表 Word(docx)
|
||||
*
|
||||
* 路由:GET /oee/line/acid/exportWord?startDate=yyyy-MM-dd&endDate=yyyy-MM-dd
|
||||
* 说明:
|
||||
* - 文档结构尽量贴近前端页面(表格为主,图表不嵌入);
|
||||
* - 将右侧“公式与口径说明”置于文档最上方。
|
||||
*/
|
||||
@GetMapping("/acid/exportWord")
|
||||
public void exportAcidWord(
|
||||
@RequestParam String startDate,
|
||||
@RequestParam String endDate,
|
||||
HttpServletResponse response
|
||||
) throws IOException {
|
||||
String[] range = resolveDateRange(startDate, endDate);
|
||||
String start = range[0];
|
||||
String end = range[1];
|
||||
|
||||
// 取数:按选择范围实时拉取(避免 summary/loss7 的“当月固定”逻辑)
|
||||
List<AcidOeeDailySummaryVo> summary = acidOeeService.getDailySummary(start, end);
|
||||
List<AcidOeeLoss7Vo> loss7 = acidOeeService.getLoss7Summary(start, end);
|
||||
List<Klptcm1ProStoppageVo> events = acidOeeService.getStoppageEvents(start, end);
|
||||
AcidOeeIdealCycleVo idealCycle = acidOeeService.getIdealCycle(start, end);
|
||||
|
||||
XWPFDocument doc = new XWPFDocument();
|
||||
|
||||
// 标题
|
||||
addTitle(doc, String.format("酸轧线OEE报表(%s ~ %s)", start, end));
|
||||
|
||||
// 口径说明(置顶)
|
||||
addSectionTitle(doc, "公式与口径说明");
|
||||
addParagraph(doc, "OEE 总公式:OEE = A × P × Q", true);
|
||||
addBullet(doc, "A(时间稼动率) = (负荷时间 − 停机时间) / 负荷时间");
|
||||
addBullet(doc, "P(性能稼动率,吨维度) = (理论节拍 × 产量吨) / 实际运转时间");
|
||||
addBullet(doc, "Q(良品率) = 良品吨 / 总产量吨");
|
||||
addParagraph(doc, "关键字段定义:", true);
|
||||
addBullet(doc, "负荷时间:计划生产时间扣除计划停机后的时间");
|
||||
addBullet(doc, "停机时间:所有停机/中断(按 stop_type 汇总)的总时长");
|
||||
addBullet(doc, "实际运转时间:负荷时间 − 停机时间");
|
||||
addBullet(doc, "理论节拍:按“优良日统计口径”得到的稳定节拍(分钟/吨)");
|
||||
addBullet(doc, "良品/次品:按 WMS quality_status 判断,C+/C/C-/D+/D/D- 为次品");
|
||||
|
||||
if (idealCycle != null) {
|
||||
addParagraph(doc, "当前理论节拍(统计口径):", true);
|
||||
XWPFTable t = doc.createTable(5, 2);
|
||||
setTableHeaderRow(t.getRow(0), "指标", "值");
|
||||
setTableRow(t.getRow(1), "理论节拍 (min/吨)", fmtNum(getIdealCycleTime(idealCycle), 2));
|
||||
setTableRow(t.getRow(2), "生产节拍中位数 (min/吨)", fmtNum(getMedianCycleTime(idealCycle), 2));
|
||||
setTableRow(t.getRow(3), "样本天数", String.valueOf(getSampleDays(idealCycle)));
|
||||
setTableRow(t.getRow(4), "吨数下限 (吨)", fmtNum(getMinWeightTon(idealCycle), 2));
|
||||
}
|
||||
|
||||
// 一、KPI 总览
|
||||
addSectionTitle(doc, "一、KPI 总览(酸轧线 SY)");
|
||||
addParagraph(doc, "展示所选期间酸轧线整体 OEE 及 A/P/Q 等关键指标,用于快速判断本期综合表现好坏。", false);
|
||||
AcidKpiAgg kpi = calcKpi(summary);
|
||||
XWPFTable kpiTable = doc.createTable(2, 11);
|
||||
setTableRow(kpiTable.getRow(0),
|
||||
"OEE (%)",
|
||||
"时间稼动率 A (%)",
|
||||
"性能稼动率 P_ton (%)",
|
||||
"良品率 Q (%)",
|
||||
"负荷时间 (min)",
|
||||
"停机时间 (min)",
|
||||
"运转时间 (min)",
|
||||
"总产量 (吨)",
|
||||
"总产量 (卷)",
|
||||
"良品量 (吨)",
|
||||
"次品量 (吨)"
|
||||
);
|
||||
setTableRow(kpiTable.getRow(1),
|
||||
fmtPercent1(kpi.oee),
|
||||
fmtPercent1(kpi.availability),
|
||||
fmtPercent1(kpi.performanceTon),
|
||||
fmtPercent1(kpi.quality),
|
||||
fmtInt(kpi.loadingTimeMin),
|
||||
fmtInt(kpi.downtimeMin),
|
||||
fmtInt(kpi.runTimeMin),
|
||||
fmtNum(kpi.totalOutputTon, 2),
|
||||
fmtInt(kpi.totalOutputCoil),
|
||||
fmtNum(kpi.goodOutputTon, 2),
|
||||
fmtNum(kpi.defectOutputTon, 2)
|
||||
);
|
||||
addAiAnalysisIfEnabled(doc, "KPI 总览", buildMarkdownTable(
|
||||
new String[]{"OEE(%)", "A(%)", "P_ton(%)", "Q(%)", "负荷(min)", "停机(min)", "运转(min)", "总产(吨)", "总产(卷)", "良品(吨)", "次品(吨)"},
|
||||
new String[][]{{
|
||||
fmtPercent1(kpi.oee),
|
||||
fmtPercent1(kpi.availability),
|
||||
fmtPercent1(kpi.performanceTon),
|
||||
fmtPercent1(kpi.quality),
|
||||
fmtInt(kpi.loadingTimeMin),
|
||||
fmtInt(kpi.downtimeMin),
|
||||
fmtInt(kpi.runTimeMin),
|
||||
fmtNum(kpi.totalOutputTon, 2),
|
||||
fmtInt(kpi.totalOutputCoil),
|
||||
fmtNum(kpi.goodOutputTon, 2),
|
||||
fmtNum(kpi.defectOutputTon, 2)
|
||||
}}), "区间:" + start + " ~ " + end);
|
||||
|
||||
// 二、日明细
|
||||
addSectionTitle(doc, "二、日明细(用于趋势分析)");
|
||||
addParagraph(doc, "按天拆分 A/P/Q 及产量等指标,用于观察趋势、波动点以及与重大事件的对应关系。", false);
|
||||
XWPFTable dailyTable = doc.createTable(Math.max(1, summary.size()) + 1, 11);
|
||||
setTableRow(dailyTable.getRow(0),
|
||||
"日期",
|
||||
"OEE (%)",
|
||||
"A (%)",
|
||||
"P_ton (%)",
|
||||
"Q (%)",
|
||||
"负荷 (min)",
|
||||
"停机 (min)",
|
||||
"运转 (min)",
|
||||
"总产量 (吨)",
|
||||
"总产量 (卷)",
|
||||
"良品 (吨)"
|
||||
);
|
||||
for (int i = 0; i < summary.size(); i++) {
|
||||
AcidOeeDailySummaryVo row = summary.get(i);
|
||||
setTableRow(dailyTable.getRow(i + 1),
|
||||
safeStr(row.getStatDate()),
|
||||
fmtPercent1(row.getOee()),
|
||||
fmtPercent1(row.getAvailability()),
|
||||
fmtPercent1(row.getPerformanceTon()),
|
||||
fmtPercent1(row.getQuality()),
|
||||
fmtInt(row.getLoadingTimeMin()),
|
||||
fmtInt(row.getDowntimeMin()),
|
||||
fmtInt(row.getRunTimeMin()),
|
||||
fmtNum(row.getTotalOutputTon(), 2),
|
||||
fmtInt(row.getTotalOutputCoil()),
|
||||
fmtNum(row.getGoodOutputTon(), 2)
|
||||
);
|
||||
}
|
||||
addAiAnalysisIfEnabled(doc, "日明细", buildDailySummaryMarkdown(summary, 31), "区间:" + start + " ~ " + end);
|
||||
|
||||
// 三、理论节拍(说明 + 表)
|
||||
addSectionTitle(doc, "三、理论节拍(统计口径)");
|
||||
addParagraph(doc, "基于历史“优良日”统计得到的理论节拍(中位数),用于作为性能稼动率计算的稳定标尺。", false);
|
||||
if (idealCycle == null) {
|
||||
addParagraph(doc, "(本区间未获取到理论节拍数据)", false);
|
||||
}
|
||||
|
||||
// 四、7 大损失
|
||||
addSectionTitle(doc, "四、7 大损失汇总(按 stop_type 分类)");
|
||||
addParagraph(doc, "将所有停机事件按 stop_type 归类,统计时间占比和次数,用于确定“先从哪几类损失下手改善”。", false);
|
||||
XWPFTable loss7Table = doc.createTable(Math.max(1, loss7.size()) + 1, 5);
|
||||
setTableRow(loss7Table.getRow(0),
|
||||
"损失类别",
|
||||
"损失时间 (min)",
|
||||
"占比 (%)",
|
||||
"次数",
|
||||
"平均时长 (min)"
|
||||
);
|
||||
for (int i = 0; i < loss7.size(); i++) {
|
||||
AcidOeeLoss7Vo row = loss7.get(i);
|
||||
setTableRow(loss7Table.getRow(i + 1),
|
||||
safeStr(row.getLossCategoryName()),
|
||||
fmtNum(row.getLossTimeMin(), 2),
|
||||
fmtPercent1(row.getLossTimeRate()),
|
||||
fmtInt(row.getCount()),
|
||||
fmtNum(row.getAvgDurationMin(), 2)
|
||||
);
|
||||
}
|
||||
addAiAnalysisIfEnabled(doc, "7 大损失汇总", buildLoss7Markdown(loss7, 30), "区间:" + start + " ~ " + end);
|
||||
|
||||
// 五、停机/损失事件明细
|
||||
addSectionTitle(doc, "五、停机/损失事件明细");
|
||||
addParagraph(doc, "罗列每一条停机/损失事件,包含时间段、区域、机组和备注,方便对照现场记录进行原因分析。", false);
|
||||
int eventRows = Math.max(1, events.size()) + 1;
|
||||
XWPFTable eventTable = doc.createTable(eventRows, 9);
|
||||
setTableRow(eventTable.getRow(0),
|
||||
"开始时间",
|
||||
"结束时间",
|
||||
"时长 (s)",
|
||||
"时长 (min)",
|
||||
"停机类型 (stop_type)",
|
||||
"区域",
|
||||
"机组",
|
||||
"班次",
|
||||
"备注"
|
||||
);
|
||||
for (int i = 0; i < events.size(); i++) {
|
||||
Klptcm1ProStoppageVo e = events.get(i);
|
||||
long durationSec = safeLong(e.getDuration());
|
||||
double durationMin = durationSec / 60d;
|
||||
setTableRow(eventTable.getRow(i + 1),
|
||||
safeStr(e.getStartDate()),
|
||||
safeStr(e.getEndDate()),
|
||||
String.valueOf(durationSec),
|
||||
fmtNum(durationMin, 3),
|
||||
safeStr(e.getStopType()),
|
||||
safeStr(e.getArea()),
|
||||
safeStr(e.getUnit()),
|
||||
safeStr(e.getShift()),
|
||||
safeStr(e.getRemark())
|
||||
);
|
||||
}
|
||||
addAiAnalysisIfEnabled(doc, "停机/损失事件明细", buildEventsMarkdown(events, 40), "区间:" + start + " ~ " + end);
|
||||
|
||||
String filename = String.format("酸轧线OEE报表_%s_%s.docx", start, end);
|
||||
String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8.name());
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename*=UTF-8''" + encoded);
|
||||
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store, no-cache");
|
||||
|
||||
try (ServletOutputStream os = response.getOutputStream()) {
|
||||
doc.write(os);
|
||||
os.flush();
|
||||
} finally {
|
||||
doc.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 若未显式传入日期范围,则默认当前月 [1号, 今天]。
|
||||
*/
|
||||
@@ -219,6 +447,315 @@ public class OeeReportController extends BaseController {
|
||||
return isStart ? firstDay.format(DATE_FORMATTER) : today.format(DATE_FORMATTER);
|
||||
}
|
||||
|
||||
// ======================== Word 生成辅助方法 ========================
|
||||
|
||||
private void addTitle(XWPFDocument doc, String text) {
|
||||
XWPFParagraph p = doc.createParagraph();
|
||||
p.setAlignment(ParagraphAlignment.CENTER);
|
||||
XWPFRun r = p.createRun();
|
||||
r.setBold(true);
|
||||
r.setFontSize(16);
|
||||
r.setText(text);
|
||||
}
|
||||
|
||||
private void addSectionTitle(XWPFDocument doc, String text) {
|
||||
XWPFParagraph p = doc.createParagraph();
|
||||
p.setSpacingBefore(200);
|
||||
XWPFRun r = p.createRun();
|
||||
r.setBold(true);
|
||||
r.setFontSize(12);
|
||||
r.setText(text);
|
||||
}
|
||||
|
||||
private void addParagraph(XWPFDocument doc, String text, boolean bold) {
|
||||
XWPFParagraph p = doc.createParagraph();
|
||||
XWPFRun r = p.createRun();
|
||||
r.setBold(bold);
|
||||
r.setFontSize(11);
|
||||
r.setText(text);
|
||||
}
|
||||
|
||||
private void addBullet(XWPFDocument doc, String text) {
|
||||
XWPFParagraph p = doc.createParagraph();
|
||||
XWPFRun r = p.createRun();
|
||||
r.setFontSize(11);
|
||||
r.setText("• " + text);
|
||||
}
|
||||
|
||||
private void addAiAnalysisIfEnabled(XWPFDocument doc, String tableTitle, String tableMarkdown, String extraContext) {
|
||||
String analysis = null;
|
||||
try {
|
||||
analysis = oeeWordAiAnalysisService.analyzeTable(tableTitle, tableMarkdown, extraContext);
|
||||
} catch (Exception e) {
|
||||
// 不影响导出
|
||||
}
|
||||
if (StringUtils.isNotBlank(analysis)) {
|
||||
addParagraph(doc, "AI 分析:", true);
|
||||
// 多行按段落输出
|
||||
String[] lines = analysis.split("\\r?\\n");
|
||||
for (String line : lines) {
|
||||
if (StringUtils.isNotBlank(line)) {
|
||||
addBullet(doc, line.trim().replaceFirst("^[\\-•\\*\\d\\.\\s]+", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildMarkdownTable(String[] headers, String[][] rows) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("|");
|
||||
for (String h : headers) sb.append(escapePipe(h)).append("|");
|
||||
sb.append("\n|");
|
||||
for (int i = 0; i < headers.length; i++) sb.append("---|");
|
||||
sb.append("\n");
|
||||
for (String[] r : rows) {
|
||||
sb.append("|");
|
||||
for (String c : r) sb.append(escapePipe(c)).append("|");
|
||||
sb.append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String escapePipe(String s) {
|
||||
if (s == null) return "";
|
||||
return s.replace("|", "\\|");
|
||||
}
|
||||
|
||||
private String buildDailySummaryMarkdown(List<AcidOeeDailySummaryVo> list, int maxRows) {
|
||||
List<AcidOeeDailySummaryVo> src = list == null ? new ArrayList<>() : list;
|
||||
int n = Math.min(src.size(), Math.max(1, maxRows));
|
||||
String[] headers = new String[]{"日期", "OEE(%)", "A(%)", "P_ton(%)", "Q(%)", "负荷(min)", "停机(min)", "运转(min)", "总产(吨)", "良品(吨)"};
|
||||
String[][] rows = new String[n][headers.length];
|
||||
for (int i = 0; i < n; i++) {
|
||||
AcidOeeDailySummaryVo r = src.get(i);
|
||||
rows[i] = new String[]{
|
||||
safeStr(r.getStatDate()),
|
||||
fmtPercent1(r.getOee()),
|
||||
fmtPercent1(r.getAvailability()),
|
||||
fmtPercent1(r.getPerformanceTon()),
|
||||
fmtPercent1(r.getQuality()),
|
||||
fmtInt(r.getLoadingTimeMin()),
|
||||
fmtInt(r.getDowntimeMin()),
|
||||
fmtInt(r.getRunTimeMin()),
|
||||
fmtNum(r.getTotalOutputTon(), 2),
|
||||
fmtNum(r.getGoodOutputTon(), 2)
|
||||
};
|
||||
}
|
||||
String md = buildMarkdownTable(headers, rows);
|
||||
if (src.size() > n) {
|
||||
md += "\n(仅展示前 " + n + " 行,已截断)\n";
|
||||
}
|
||||
return md;
|
||||
}
|
||||
|
||||
private String buildLoss7Markdown(List<AcidOeeLoss7Vo> list, int maxRows) {
|
||||
List<AcidOeeLoss7Vo> src = list == null ? new ArrayList<>() : list;
|
||||
int n = Math.min(src.size(), Math.max(1, maxRows));
|
||||
String[] headers = new String[]{"损失类别", "损失时间(min)", "占比(%)", "次数", "平均时长(min)"};
|
||||
String[][] rows = new String[n][headers.length];
|
||||
for (int i = 0; i < n; i++) {
|
||||
AcidOeeLoss7Vo r = src.get(i);
|
||||
rows[i] = new String[]{
|
||||
safeStr(r.getLossCategoryName()),
|
||||
fmtNum(r.getLossTimeMin(), 2),
|
||||
fmtPercent1(r.getLossTimeRate()),
|
||||
fmtInt(r.getCount()),
|
||||
fmtNum(r.getAvgDurationMin(), 2)
|
||||
};
|
||||
}
|
||||
String md = buildMarkdownTable(headers, rows);
|
||||
if (src.size() > n) {
|
||||
md += "\n(仅展示前 " + n + " 行,已截断)\n";
|
||||
}
|
||||
return md;
|
||||
}
|
||||
|
||||
private String buildEventsMarkdown(List<Klptcm1ProStoppageVo> list, int maxRows) {
|
||||
List<Klptcm1ProStoppageVo> src = list == null ? new ArrayList<>() : list;
|
||||
int n = Math.min(src.size(), Math.max(1, maxRows));
|
||||
String[] headers = new String[]{"开始", "结束", "时长(s)", "时长(min)", "停机类型", "区域", "机组", "备注"};
|
||||
String[][] rows = new String[n][headers.length];
|
||||
for (int i = 0; i < n; i++) {
|
||||
Klptcm1ProStoppageVo e = src.get(i);
|
||||
long durationSec = safeLong(e.getDuration());
|
||||
rows[i] = new String[]{
|
||||
safeStr(e.getStartDate()),
|
||||
safeStr(e.getEndDate()),
|
||||
String.valueOf(durationSec),
|
||||
fmtNum(durationSec / 60d, 3),
|
||||
safeStr(e.getStopType()),
|
||||
safeStr(e.getArea()),
|
||||
safeStr(e.getUnit()),
|
||||
safeStr(e.getRemark())
|
||||
};
|
||||
}
|
||||
String md = buildMarkdownTable(headers, rows);
|
||||
md += "\n总事件数:" + src.size() + "\n";
|
||||
if (src.size() > n) {
|
||||
md += "(仅展示前 " + n + " 行,已截断)\n";
|
||||
}
|
||||
return md;
|
||||
}
|
||||
|
||||
private void setTableHeaderRow(XWPFTableRow row, String c1, String c2) {
|
||||
setCellText(row.getCell(0), c1, true);
|
||||
setCellText(row.getCell(1), c2, true);
|
||||
}
|
||||
|
||||
private void setTableRow(XWPFTableRow row, String... cols) {
|
||||
for (int i = 0; i < cols.length; i++) {
|
||||
XWPFTableCell cell = row.getCell(i);
|
||||
if (cell == null) {
|
||||
cell = row.createCell();
|
||||
}
|
||||
setCellText(cell, cols[i], false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCellText(XWPFTableCell cell, String text, boolean bold) {
|
||||
cell.removeParagraph(0);
|
||||
XWPFParagraph p = cell.addParagraph();
|
||||
XWPFRun r = p.createRun();
|
||||
r.setBold(bold);
|
||||
r.setFontSize(10);
|
||||
r.setText(text == null ? "" : text);
|
||||
}
|
||||
|
||||
private String safeStr(Object o) {
|
||||
return o == null ? "" : String.valueOf(o);
|
||||
}
|
||||
|
||||
private long safeLong(Object o) {
|
||||
if (o == null) return 0L;
|
||||
try {
|
||||
return Long.parseLong(String.valueOf(o));
|
||||
} catch (Exception ex) {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private String fmtPercent1(Object value) {
|
||||
Double v = toDouble(value);
|
||||
if (v == null) return "-";
|
||||
return String.format(Locale.ROOT, "%.1f", v);
|
||||
}
|
||||
|
||||
private String fmtNum(Object value, int scale) {
|
||||
Double v = toDouble(value);
|
||||
if (v == null) return "-";
|
||||
return String.format(Locale.ROOT, "%." + scale + "f", v);
|
||||
}
|
||||
|
||||
private String fmtInt(Object value) {
|
||||
Double v = toDouble(value);
|
||||
if (v == null) return "0";
|
||||
return String.valueOf((long) Math.round(v));
|
||||
}
|
||||
|
||||
private Double toDouble(Object value) {
|
||||
if (value == null) return null;
|
||||
if (value instanceof Number) return ((Number) value).doubleValue();
|
||||
String s = String.valueOf(value);
|
||||
if (StringUtils.isBlank(s)) return null;
|
||||
try {
|
||||
return Double.parseDouble(s);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class AcidKpiAgg {
|
||||
private double oee;
|
||||
private double availability;
|
||||
private double performanceTon;
|
||||
private double quality;
|
||||
private double loadingTimeMin;
|
||||
private double downtimeMin;
|
||||
private double runTimeMin;
|
||||
private double totalOutputTon;
|
||||
private double totalOutputCoil;
|
||||
private double goodOutputTon;
|
||||
private double defectOutputTon;
|
||||
}
|
||||
|
||||
private AcidKpiAgg calcKpi(List<AcidOeeDailySummaryVo> list) {
|
||||
AcidKpiAgg k = new AcidKpiAgg();
|
||||
if (list == null || list.isEmpty()) {
|
||||
return k;
|
||||
}
|
||||
int n = list.size();
|
||||
double sumOee = 0, sumA = 0, sumP = 0, sumQ = 0;
|
||||
double sumLoading = 0, sumDown = 0, sumRun = 0;
|
||||
double sumTotalTon = 0, sumGoodTon = 0, sumDefectTon = 0, sumCoil = 0;
|
||||
for (AcidOeeDailySummaryVo r : list) {
|
||||
sumLoading += safeNum(r.getLoadingTimeMin());
|
||||
sumDown += safeNum(r.getDowntimeMin());
|
||||
sumRun += safeNum(r.getRunTimeMin());
|
||||
sumTotalTon += safeNum(r.getTotalOutputTon());
|
||||
sumGoodTon += safeNum(r.getGoodOutputTon());
|
||||
sumDefectTon += safeNum(r.getDefectOutputTon());
|
||||
sumCoil += safeNum(r.getTotalOutputCoil());
|
||||
sumOee += safeNum(r.getOee());
|
||||
sumA += safeNum(r.getAvailability());
|
||||
sumP += safeNum(r.getPerformanceTon());
|
||||
sumQ += safeNum(r.getQuality());
|
||||
}
|
||||
double defectAgg = Math.max(0, sumTotalTon - sumGoodTon);
|
||||
k.setOee(sumOee / n);
|
||||
k.setAvailability(sumA / n);
|
||||
k.setPerformanceTon(sumP / n);
|
||||
k.setQuality(sumQ / n);
|
||||
k.setLoadingTimeMin(sumLoading);
|
||||
k.setDowntimeMin(sumDown);
|
||||
k.setRunTimeMin(sumRun);
|
||||
k.setTotalOutputTon(sumTotalTon);
|
||||
k.setTotalOutputCoil(sumCoil);
|
||||
k.setGoodOutputTon(sumGoodTon);
|
||||
k.setDefectOutputTon(defectAgg > 0 ? defectAgg : sumDefectTon);
|
||||
return k;
|
||||
}
|
||||
|
||||
private double safeNum(Object v) {
|
||||
Double d = toDouble(v);
|
||||
return d == null ? 0d : d;
|
||||
}
|
||||
|
||||
// idealCycle 字段做容错(避免 VO 字段名差异导致编译失败)
|
||||
private Object getIdealCycleTime(AcidOeeIdealCycleVo vo) {
|
||||
try {
|
||||
return vo.getClass().getMethod("getIdealCycleTimeMinPerTon").invoke(vo);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getMedianCycleTime(AcidOeeIdealCycleVo vo) {
|
||||
try {
|
||||
return vo.getClass().getMethod("getMedianCycleTimeMinPerTon").invoke(vo);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private int getSampleDays(AcidOeeIdealCycleVo vo) {
|
||||
try {
|
||||
Object v = vo.getClass().getMethod("getSampleDays").invoke(vo);
|
||||
Double d = toDouble(v);
|
||||
return d == null ? 0 : d.intValue();
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getMinWeightTon(AcidOeeIdealCycleVo vo) {
|
||||
try {
|
||||
return vo.getClass().getMethod("getMinWeightTon").invoke(vo);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 任意日期范围异步任务(酸轧线) ========================
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.klp.da.task;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo;
|
||||
import com.klp.pocket.acid.service.IAcidOeeService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -25,10 +24,9 @@ import java.util.concurrent.TimeUnit;
|
||||
* - 项目启动完成后即计算当月 OEE 聚合结果并写入 Redis;
|
||||
* - 每天凌晨 04:00 重新计算当月数据并覆盖缓存。
|
||||
*
|
||||
* 当前仅实现酸轧线(SY)的当月日汇总 & 7 大损失预计算;
|
||||
* 当前仅实现酸轧线(SY)的当月日汇总预计算;
|
||||
* key 约定:
|
||||
* - 汇总结果:oee:report:month:summary:{yyyyMM}:SY
|
||||
* - 7 大损失:oee:report:month:loss7:{yyyyMM}:SY
|
||||
* - 元信息: oee:report:month:meta:{yyyyMM}:SY
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -39,9 +37,6 @@ public class AcidOeeMonthTask {
|
||||
/** Redis 缓存 key 模板:当月 OEE 汇总(酸轧线) */
|
||||
private static final String SUMMARY_KEY_PATTERN = "oee:report:month:summary:%s:SY";
|
||||
|
||||
/** Redis 缓存 key 模板:当月 7 大损失(酸轧线) */
|
||||
private static final String LOSS7_KEY_PATTERN = "oee:report:month:loss7:%s:SY";
|
||||
|
||||
/** Redis 缓存 key 模板:当月元信息(酸轧线) */
|
||||
private static final String META_KEY_PATTERN = "oee:report:month:meta:%s:SY";
|
||||
|
||||
@@ -96,20 +91,14 @@ public class AcidOeeMonthTask {
|
||||
log.info("[AcidOeeMonthTask] trigger={}, computing acid OEE month summary for {} ({} ~ {})",
|
||||
trigger, yyyyMM, startStr, endStr);
|
||||
|
||||
// 1. 调用 pocket 的 AcidOeeService 获取当月日汇总 & 7 大损失
|
||||
// 1. 调用 pocket 的 AcidOeeService 获取当月日汇总
|
||||
List<AcidOeeDailySummaryVo> dailySummaryList = acidOeeService.getDailySummary(startStr, endStr);
|
||||
List<AcidOeeLoss7Vo> loss7List = acidOeeService.getLoss7Summary(startStr, endStr);
|
||||
|
||||
// 2. 写入 Redis(summary)
|
||||
String summaryKey = String.format(SUMMARY_KEY_PATTERN, yyyyMM);
|
||||
String summaryJson = JSON.toJSONString(dailySummaryList);
|
||||
stringRedisTemplate.opsForValue().set(summaryKey, summaryJson, 1, TimeUnit.DAYS);
|
||||
|
||||
// 2.1 写入 Redis(loss7)
|
||||
String loss7Key = String.format(LOSS7_KEY_PATTERN, yyyyMM);
|
||||
String loss7Json = JSON.toJSONString(loss7List);
|
||||
stringRedisTemplate.opsForValue().set(loss7Key, loss7Json, 1, TimeUnit.DAYS);
|
||||
|
||||
long durationMs = (System.nanoTime() - startNs) / 1_000_000L;
|
||||
|
||||
// 3. 写入 Redis(meta)
|
||||
@@ -123,8 +112,8 @@ public class AcidOeeMonthTask {
|
||||
String metaKey = String.format(META_KEY_PATTERN, yyyyMM);
|
||||
stringRedisTemplate.opsForValue().set(metaKey, JSON.toJSONString(meta), 1, TimeUnit.DAYS);
|
||||
|
||||
log.info("[AcidOeeMonthTask] compute finish for {} dailySize={}, loss7Size={}, durationMs={}ms, summaryKey={}",
|
||||
yyyyMM, dailySummaryList.size(), loss7List.size(), durationMs, summaryKey);
|
||||
log.info("[AcidOeeMonthTask] compute finish for {} dailySize={}, durationMs={}ms, summaryKey={}",
|
||||
yyyyMM, dailySummaryList.size(), durationMs, summaryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user