酸轧OEE后端重构完成
This commit is contained in:
@@ -1,26 +1,43 @@
|
||||
package com.klp.da.controller;
|
||||
|
||||
import com.klp.common.annotation.Log;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
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.OeeReportJobService;
|
||||
import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo;
|
||||
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.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* OEE 报表聚合 Controller(方式 A:后端统一聚合多服务)
|
||||
* OEE 报表对外接口(聚合层)
|
||||
*
|
||||
* 路由前缀与 docs/oee-report-design.md 设计文档保持一致:
|
||||
* - /api/ems/oee/line/summary
|
||||
* - /api/ems/oee/line/loss7
|
||||
* - /api/ems/oee/line/events
|
||||
* - /api/ems/oee/line/exportWord
|
||||
* 当前阶段:主要暴露酸轧线相关接口,通过 `klp-pocket` 的 {@link IAcidOeeService} 取数。
|
||||
* - 当月 summary / loss7 优先走 Redis 预计算缓存;
|
||||
* - 任意日期范围通过异步任务接口实现。
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@@ -28,6 +45,304 @@ import java.util.Map;
|
||||
@RequestMapping("/oee/line")
|
||||
public class OeeReportController extends BaseController {
|
||||
|
||||
private final IAcidOeeService acidOeeService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final OeeReportJobService oeeReportJobService;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
private static final String JOB_META_KEY_PATTERN = "oee:report:job:meta:%s";
|
||||
private static final String JOB_RESULT_KEY_PATTERN = "oee:report:job:result:%s";
|
||||
|
||||
/**
|
||||
* 酸轧线 OEE 日汇总(含趋势)
|
||||
*
|
||||
* 路由:GET /oee/line/acid/summary
|
||||
* 说明:
|
||||
* - 不接受 start/end 参数,固定返回“当前月份(1号~今天)”的当月预计算结果;
|
||||
* - 优先从 Redis 当月缓存读取;若缓存缺失则实时计算一次当前月。
|
||||
*/
|
||||
@GetMapping("/acid/summary")
|
||||
public R<List<AcidOeeDailySummaryVo>> getAcidSummary() {
|
||||
String yyyyMM = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
String summaryKey = String.format("oee:report:month:summary:%s:SY", yyyyMM);
|
||||
|
||||
// 1. 优先从 Redis 读取当月预计算结果
|
||||
String json = stringRedisTemplate.opsForValue().get(summaryKey);
|
||||
if (StringUtils.isNotBlank(json)) {
|
||||
List<AcidOeeDailySummaryVo> cached =
|
||||
JSON.parseArray(json, AcidOeeDailySummaryVo.class);
|
||||
return R.ok(cached);
|
||||
}
|
||||
|
||||
// 2. 缓存缺失时,回退为实时计算当前月
|
||||
String[] range = resolveDateRange(null, null);
|
||||
List<AcidOeeDailySummaryVo> dailyList =
|
||||
acidOeeService.getDailySummary(range[0], range[1]);
|
||||
return R.ok(dailyList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 酸轧线 7 大损失汇总
|
||||
*
|
||||
* 路由:GET /oee/line/acid/loss7
|
||||
* 说明:
|
||||
* - 不接受 start/end 参数,固定返回“当前月份(1号~今天)”的当月预计算结果;
|
||||
* - {@code topN} 用于限制返回的损失类别条数(按损失时间降序截取)。
|
||||
*/
|
||||
@GetMapping("/acid/loss7")
|
||||
public R<List<AcidOeeLoss7Vo>> getAcidLoss7(
|
||||
@RequestParam(required = false, defaultValue = "50") Integer topN
|
||||
) {
|
||||
String yyyyMM = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
||||
String loss7Key = String.format("oee:report:month:loss7:%s:SY", yyyyMM);
|
||||
|
||||
// 1. 优先从 Redis 读取当月 7 大损失预计算结果
|
||||
String json = stringRedisTemplate.opsForValue().get(loss7Key);
|
||||
List<AcidOeeLoss7Vo> lossList;
|
||||
if (StringUtils.isNotBlank(json)) {
|
||||
lossList = JSON.parseArray(json, AcidOeeLoss7Vo.class);
|
||||
} else {
|
||||
// 2. 缓存缺失时,回退为实时计算当前月
|
||||
String[] range = resolveDateRange(null, null);
|
||||
lossList = acidOeeService.getLoss7Summary(range[0], range[1]);
|
||||
}
|
||||
|
||||
if (topN != null && topN > 0 && lossList.size() > topN) {
|
||||
lossList = new ArrayList<>(lossList.subList(0, topN));
|
||||
}
|
||||
|
||||
return R.ok(lossList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 酸轧线停机/损失事件明细(分页)
|
||||
*
|
||||
* 路由:GET /oee/line/acid/events
|
||||
* 说明:
|
||||
* - 若未传入开始/结束时间,则默认查询当月。
|
||||
* - 当前实现为在内存中进行简单过滤与分页,后续可按需下沉到 pocket 或 Mapper。
|
||||
*/
|
||||
@GetMapping("/acid/events")
|
||||
public TableDataInfo<Klptcm1ProStoppageVo> getAcidEvents(
|
||||
@RequestParam(required = false) String startTime,
|
||||
@RequestParam(required = false) String endTime,
|
||||
@RequestParam(required = false) String stopType,
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(required = false, defaultValue = "10") Integer pageSize
|
||||
) {
|
||||
// 事件明细底层按「日期」查询,这里从时间字符串中截取日期部分
|
||||
String startDate = extractDateOrDefault(startTime, true);
|
||||
String endDate = extractDateOrDefault(endTime, false);
|
||||
|
||||
List<Klptcm1ProStoppageVo> events =
|
||||
acidOeeService.getStoppageEvents(startDate, endDate);
|
||||
|
||||
// 业务筛选:stopType、关键字(目前对 stopType / remark 做 contains 匹配)
|
||||
List<Klptcm1ProStoppageVo> filtered = events.stream()
|
||||
.filter(e -> {
|
||||
if (StringUtils.isNotBlank(stopType) &&
|
||||
!StringUtils.equals(stopType, e.getStopType())) {
|
||||
return false;
|
||||
}
|
||||
if (StringUtils.isBlank(keyword)) {
|
||||
return true;
|
||||
}
|
||||
String remark = e.getRemark();
|
||||
String type = e.getStopType();
|
||||
return (StringUtils.isNotBlank(remark) && remark.contains(keyword))
|
||||
|| (StringUtils.isNotBlank(type) && type.contains(keyword));
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long total = filtered.size();
|
||||
int page = (pageNum == null || pageNum < 1) ? 1 : pageNum;
|
||||
int size = (pageSize == null || pageSize < 1) ? 10 : pageSize;
|
||||
int fromIndex = (page - 1) * size;
|
||||
int toIndex = Math.min(fromIndex + size, filtered.size());
|
||||
|
||||
List<Klptcm1ProStoppageVo> pageList;
|
||||
if (fromIndex >= filtered.size()) {
|
||||
pageList = new ArrayList<>();
|
||||
} else {
|
||||
pageList = filtered.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
TableDataInfo<Klptcm1ProStoppageVo> rsp = TableDataInfo.build();
|
||||
rsp.setRows(pageList);
|
||||
rsp.setTotal(total);
|
||||
return rsp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 酸轧线理论节拍回归数据
|
||||
*
|
||||
* 路由:GET /oee/line/acid/regression
|
||||
* 说明:
|
||||
* - {@code startDate} / {@code endDate} 可选,若为空则由 pocket 按“近6个月”默认处理。
|
||||
*/
|
||||
@GetMapping("/acid/regression")
|
||||
public R<AcidOeeRegressionVo> getAcidRegression(
|
||||
@RequestParam(required = false) String startDate,
|
||||
@RequestParam(required = false) String endDate
|
||||
) {
|
||||
AcidOeeRegressionVo data = acidOeeService.getRegressionData(startDate, endDate);
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 若未显式传入日期范围,则默认当前月 [1号, 今天]。
|
||||
*/
|
||||
private String[] resolveDateRange(String startDate, String endDate) {
|
||||
if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) {
|
||||
return new String[]{startDate, endDate};
|
||||
}
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate firstDay = today.withDayOfMonth(1);
|
||||
return new String[]{firstDay.format(DATE_FORMATTER), today.format(DATE_FORMATTER)};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从完整时间字符串中截取 yyyy-MM-dd;若为空则回退到当月默认范围。
|
||||
*/
|
||||
private String extractDateOrDefault(String dateTime, boolean isStart) {
|
||||
if (StringUtils.isNotBlank(dateTime)) {
|
||||
// 期望格式 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss,统一截前 10 位
|
||||
if (dateTime.length() >= 10) {
|
||||
return dateTime.substring(0, 10);
|
||||
}
|
||||
}
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate firstDay = today.withDayOfMonth(1);
|
||||
return isStart ? firstDay.format(DATE_FORMATTER) : today.format(DATE_FORMATTER);
|
||||
}
|
||||
|
||||
// ======================== 任意日期范围异步任务(酸轧线) ========================
|
||||
|
||||
/**
|
||||
* 提交酸轧线 OEE 报表异步任务(任意日期范围)。
|
||||
*
|
||||
* 路由:POST /oee/line/acid/report-jobs
|
||||
*/
|
||||
@PostMapping("/acid/report-jobs")
|
||||
public R<OeeReportJobMeta> submitAcidReportJob(@RequestBody OeeReportJobSubmitBo body) {
|
||||
if (body == null || StringUtils.isBlank(body.getStartDate()) || StringUtils.isBlank(body.getEndDate())) {
|
||||
return R.fail("startDate/endDate 不能为空");
|
||||
}
|
||||
|
||||
String jobId = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
OeeReportJobMeta meta = new OeeReportJobMeta();
|
||||
meta.setJobId(jobId);
|
||||
meta.setStatus("PENDING");
|
||||
meta.setSubmittedAt(now.format(DATE_TIME_FORMATTER));
|
||||
meta.setStartDate(body.getStartDate());
|
||||
meta.setEndDate(body.getEndDate());
|
||||
|
||||
// 先写入 PENDING 状态
|
||||
String metaKey = String.format(JOB_META_KEY_PATTERN, jobId);
|
||||
stringRedisTemplate.opsForValue().set(metaKey, JSON.toJSONString(meta), 1, TimeUnit.DAYS);
|
||||
|
||||
boolean includeSummary = body.getIncludeSummary() == null || Boolean.TRUE.equals(body.getIncludeSummary());
|
||||
boolean includeLoss7 = body.getIncludeLoss7() == null || Boolean.TRUE.equals(body.getIncludeLoss7());
|
||||
String resultKey = String.format(JOB_RESULT_KEY_PATTERN, jobId);
|
||||
|
||||
// 异步执行实际计算
|
||||
oeeReportJobService.executeAcidReportJob(
|
||||
jobId,
|
||||
metaKey,
|
||||
resultKey,
|
||||
body.getStartDate(),
|
||||
body.getEndDate(),
|
||||
includeSummary,
|
||||
includeLoss7
|
||||
);
|
||||
|
||||
return R.ok(meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询酸轧线 OEE 报表任务状态。
|
||||
*
|
||||
* 路由:GET /oee/line/acid/report-jobs/{jobId}
|
||||
*/
|
||||
@GetMapping("/acid/report-jobs/{jobId}")
|
||||
public R<OeeReportJobMeta> getAcidReportJob(@PathVariable String jobId) {
|
||||
String metaKey = String.format(JOB_META_KEY_PATTERN, jobId);
|
||||
String json = stringRedisTemplate.opsForValue().get(metaKey);
|
||||
if (StringUtils.isBlank(json)) {
|
||||
return R.fail("任务不存在或已过期");
|
||||
}
|
||||
OeeReportJobMeta meta = JSON.parseObject(json, OeeReportJobMeta.class);
|
||||
return R.ok(meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取酸轧线 OEE 报表任务结果。
|
||||
*
|
||||
* 路由:GET /oee/line/acid/report-jobs/{jobId}/result
|
||||
*/
|
||||
@GetMapping("/acid/report-jobs/{jobId}/result")
|
||||
public R<OeeReportJobResult> getAcidReportJobResult(@PathVariable String jobId) {
|
||||
String metaKey = String.format(JOB_META_KEY_PATTERN, jobId);
|
||||
String metaJson = stringRedisTemplate.opsForValue().get(metaKey);
|
||||
if (StringUtils.isBlank(metaJson)) {
|
||||
return R.fail("任务不存在或已过期");
|
||||
}
|
||||
OeeReportJobMeta meta = JSON.parseObject(metaJson, OeeReportJobMeta.class);
|
||||
if (!"COMPLETED".equals(meta.getStatus())) {
|
||||
return R.fail("结果未就绪,当前状态:" + meta.getStatus());
|
||||
}
|
||||
|
||||
String resultKey = String.format(JOB_RESULT_KEY_PATTERN, jobId);
|
||||
String resultJson = stringRedisTemplate.opsForValue().get(resultKey);
|
||||
if (StringUtils.isBlank(resultJson)) {
|
||||
return R.fail("任务结果不存在或已过期");
|
||||
}
|
||||
|
||||
OeeReportJobResult result = JSON.parseObject(resultJson, OeeReportJobResult.class);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
// ======================== 内部 DTO ========================
|
||||
|
||||
@Data
|
||||
private static class OeeReportJobSubmitBo {
|
||||
/** 开始日期(yyyy-MM-dd) */
|
||||
private String startDate;
|
||||
/** 结束日期(yyyy-MM-dd) */
|
||||
private String endDate;
|
||||
/** 是否计算 summary(默认 true) */
|
||||
private Boolean includeSummary;
|
||||
/** 是否计算 loss7(默认 true) */
|
||||
private Boolean includeLoss7;
|
||||
/**
|
||||
* 口径开关等扩展选项(JSON 文本或键值对),当前实现暂不解析,仅作为预留字段。
|
||||
*/
|
||||
private String options;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class OeeReportJobMeta {
|
||||
private String jobId;
|
||||
/** PENDING / RUNNING / COMPLETED / FAILED / EXPIRED */
|
||||
private String status;
|
||||
private String submittedAt;
|
||||
private String startedAt;
|
||||
private String completedAt;
|
||||
private String startDate;
|
||||
private String endDate;
|
||||
private String errorMessage;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class OeeReportJobResult {
|
||||
/** 日汇总结果(用于 KPI + 趋势) */
|
||||
private List<AcidOeeDailySummaryVo> summary;
|
||||
/** 7 大损失结果 */
|
||||
private List<AcidOeeLoss7Vo> loss7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
137
klp-da/src/main/java/com/klp/da/service/OeeReportJobService.java
Normal file
137
klp-da/src/main/java/com/klp/da/service/OeeReportJobService.java
Normal file
@@ -0,0 +1,137 @@
|
||||
package com.klp.da.service;
|
||||
|
||||
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;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* OEE 报表异步任务执行 Service(酸轧线)。
|
||||
*
|
||||
* 负责在后台线程中执行任意日期范围的 summary/loss7 计算,
|
||||
* 并将任务状态与结果写入 Redis。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OeeReportJobService {
|
||||
|
||||
private final IAcidOeeService acidOeeService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
/**
|
||||
* 异步执行酸轧线报表任务。
|
||||
*
|
||||
* @param jobId 任务 ID
|
||||
* @param metaKey 任务元信息 Redis key
|
||||
* @param resultKey 任务结果 Redis key
|
||||
* @param startDate 开始日期(yyyy-MM-dd)
|
||||
* @param endDate 结束日期(yyyy-MM-dd)
|
||||
* @param includeSummary 是否计算 summary
|
||||
* @param includeLoss7 是否计算 loss7
|
||||
*/
|
||||
@Async
|
||||
public void executeAcidReportJob(
|
||||
String jobId,
|
||||
String metaKey,
|
||||
String resultKey,
|
||||
String startDate,
|
||||
String endDate,
|
||||
boolean includeSummary,
|
||||
boolean includeLoss7
|
||||
) {
|
||||
try {
|
||||
// 更新状态为 RUNNING
|
||||
OeeReportJobMeta meta = readMeta(metaKey);
|
||||
if (meta == null) {
|
||||
meta = new OeeReportJobMeta();
|
||||
meta.setJobId(jobId);
|
||||
meta.setStartDate(startDate);
|
||||
meta.setEndDate(endDate);
|
||||
}
|
||||
meta.setStatus("RUNNING");
|
||||
meta.setStartedAt(LocalDateTime.now().format(DATE_TIME_FORMATTER));
|
||||
writeMeta(metaKey, meta);
|
||||
|
||||
// 实际计算
|
||||
OeeReportJobResult result = new OeeReportJobResult();
|
||||
if (includeSummary) {
|
||||
List<AcidOeeDailySummaryVo> summary =
|
||||
acidOeeService.getDailySummary(startDate, endDate);
|
||||
result.setSummary(summary);
|
||||
}
|
||||
if (includeLoss7) {
|
||||
List<AcidOeeLoss7Vo> loss7 =
|
||||
acidOeeService.getLoss7Summary(startDate, endDate);
|
||||
result.setLoss7(loss7);
|
||||
}
|
||||
|
||||
stringRedisTemplate.opsForValue()
|
||||
.set(resultKey, JSON.toJSONString(result), 1, TimeUnit.DAYS);
|
||||
|
||||
// 标记为 COMPLETED
|
||||
meta.setStatus("COMPLETED");
|
||||
meta.setCompletedAt(LocalDateTime.now().format(DATE_TIME_FORMATTER));
|
||||
writeMeta(metaKey, meta);
|
||||
} catch (Exception e) {
|
||||
log.error("[OeeReportJobService] executeAcidReportJob error, jobId={}", jobId, e);
|
||||
OeeReportJobMeta meta = readMeta(metaKey);
|
||||
if (meta == null) {
|
||||
meta = new OeeReportJobMeta();
|
||||
meta.setJobId(jobId);
|
||||
meta.setStartDate(startDate);
|
||||
meta.setEndDate(endDate);
|
||||
}
|
||||
meta.setStatus("FAILED");
|
||||
meta.setCompletedAt(LocalDateTime.now().format(DATE_TIME_FORMATTER));
|
||||
meta.setErrorMessage(e.getMessage());
|
||||
writeMeta(metaKey, meta);
|
||||
}
|
||||
}
|
||||
|
||||
private OeeReportJobMeta readMeta(String metaKey) {
|
||||
String json = stringRedisTemplate.opsForValue().get(metaKey);
|
||||
if (json == null || json.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parseObject(json, OeeReportJobMeta.class);
|
||||
}
|
||||
|
||||
private void writeMeta(String metaKey, OeeReportJobMeta meta) {
|
||||
stringRedisTemplate.opsForValue()
|
||||
.set(metaKey, JSON.toJSONString(meta), 1, TimeUnit.DAYS);
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class OeeReportJobMeta {
|
||||
private String jobId;
|
||||
private String status;
|
||||
private String submittedAt;
|
||||
private String startedAt;
|
||||
private String completedAt;
|
||||
private String startDate;
|
||||
private String endDate;
|
||||
private String errorMessage;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class OeeReportJobResult {
|
||||
private List<AcidOeeDailySummaryVo> summary;
|
||||
private List<AcidOeeLoss7Vo> loss7;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
148
klp-da/src/main/java/com/klp/da/task/AcidOeeMonthTask.java
Normal file
148
klp-da/src/main/java/com/klp/da/task/AcidOeeMonthTask.java
Normal file
@@ -0,0 +1,148 @@
|
||||
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;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 酸轧线 OEE 当月预计算任务
|
||||
*
|
||||
* 需求对应 docs/oee-report-design.md 第 12.2 节:
|
||||
* - 项目启动完成后即计算当月 OEE 聚合结果并写入 Redis;
|
||||
* - 每天凌晨 04:00 重新计算当月数据并覆盖缓存。
|
||||
*
|
||||
* 当前仅实现酸轧线(SY)的当月日汇总 & 7 大损失预计算;
|
||||
* key 约定:
|
||||
* - 汇总结果:oee:report:month:summary:{yyyyMM}:SY
|
||||
* - 7 大损失:oee:report:month:loss7:{yyyyMM}:SY
|
||||
* - 元信息: oee:report:month:meta:{yyyyMM}:SY
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
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";
|
||||
|
||||
private static final DateTimeFormatter YEAR_MONTH_FMT = DateTimeFormatter.ofPattern("yyyyMM");
|
||||
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_DATE;
|
||||
private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
|
||||
|
||||
private final IAcidOeeService acidOeeService;
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 项目启动完成后立即计算一次当月酸轧 OEE 汇总并写入 Redis。
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
computeCurrentMonth("startup");
|
||||
} catch (Exception e) {
|
||||
log.error("[AcidOeeMonthTask] startup compute failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每天凌晨 04:00 重新计算当月酸轧 OEE 汇总并覆盖 Redis 缓存。
|
||||
*/
|
||||
@Scheduled(cron = "0 0 4 * * ?")
|
||||
public void scheduleDaily() {
|
||||
try {
|
||||
computeCurrentMonth("schedule-04");
|
||||
} catch (Exception e) {
|
||||
log.error("[AcidOeeMonthTask] 4am compute failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前月份(从当月1号到今天)的酸轧 OEE 日汇总,并写入 Redis。
|
||||
*
|
||||
* @param trigger 触发来源标记(startup / schedule-04 等)
|
||||
*/
|
||||
private void computeCurrentMonth(String trigger) {
|
||||
long startNs = System.nanoTime();
|
||||
|
||||
LocalDate now = LocalDate.now();
|
||||
String yyyyMM = now.format(YEAR_MONTH_FMT);
|
||||
|
||||
LocalDate startDate = now.withDayOfMonth(1);
|
||||
LocalDate endDate = now;
|
||||
|
||||
String startStr = startDate.format(DATE_FMT);
|
||||
String endStr = endDate.format(DATE_FMT);
|
||||
|
||||
log.info("[AcidOeeMonthTask] trigger={}, computing acid OEE month summary for {} ({} ~ {})",
|
||||
trigger, yyyyMM, startStr, endStr);
|
||||
|
||||
// 1. 调用 pocket 的 AcidOeeService 获取当月日汇总 & 7 大损失
|
||||
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)
|
||||
Meta meta = new Meta();
|
||||
meta.setComputedAt(LocalDateTime.now().format(DATE_TIME_FMT));
|
||||
meta.setDurationMs(durationMs);
|
||||
meta.setStartDate(startStr);
|
||||
meta.setEndDate(endStr);
|
||||
meta.setTrigger(trigger);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当月预计算元信息
|
||||
*/
|
||||
@Data
|
||||
private static class Meta {
|
||||
/** 计算完成时间(ISO-8601 字符串) */
|
||||
private String computedAt;
|
||||
/** 计算耗时(毫秒) */
|
||||
private long durationMs;
|
||||
/** 统计起始日期(yyyy-MM-dd) */
|
||||
private String startDate;
|
||||
/** 统计结束日期(yyyy-MM-dd) */
|
||||
private String endDate;
|
||||
/** 触发来源(startup / schedule-04 等) */
|
||||
private String trigger;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user