OEE初版,错误问题和交互问题1.29再说

This commit is contained in:
2026-01-28 18:40:53 +08:00
parent 2e0638c03e
commit ef68690cc1
21 changed files with 2789 additions and 524 deletions

View File

@@ -8,10 +8,10 @@ import com.klp.common.core.page.TableDataInfo;
import com.klp.common.enums.BusinessType;
import com.klp.da.domain.bo.OeeQueryBo;
import com.klp.da.domain.vo.OeeLineSummaryVo;
import com.klp.da.domain.vo.OeeLossCategorySummaryVo;
import com.klp.da.domain.vo.OeeLossReasonVo;
import com.klp.da.domain.vo.OeeEventVo;
import com.klp.da.service.IOeeReportService;
import com.klp.da.service.OeeSummaryJobService;
import com.klp.da.service.OeeTheoryCycleJobService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
@@ -40,6 +40,8 @@ import java.util.Map;
public class OeeReportController extends BaseController {
private final IOeeReportService oeeReportService;
private final OeeTheoryCycleJobService oeeTheoryCycleJobService;
private final OeeSummaryJobService oeeSummaryJobService;
/**
* KPI + 趋势汇总
@@ -50,6 +52,24 @@ public class OeeReportController extends BaseController {
return R.ok(oeeReportService.summary(queryBo));
}
/**
* KPI + 趋势汇总(异步任务):立即返回 jobId通过 WebSocket 推送进度与结果。
*
* 前端订阅:
* ws://{host}/websocket?type={wsType}
*/
@Log(title = "OEE 报表-汇总(异步)", businessType = BusinessType.OTHER)
@GetMapping("/summary/job")
public R<Map<String, Object>> summaryJob(OeeQueryBo queryBo) {
System.out.println("[OEE][summary][job][controller] request received, thread=" + Thread.currentThread().getName()
+ ", startDate=" + (queryBo == null ? null : queryBo.getStartDate())
+ ", endDate=" + (queryBo == null ? null : queryBo.getEndDate())
+ ", lineIds=" + (queryBo == null ? null : queryBo.getLineIds()));
Map<String, Object> resp = oeeSummaryJobService.createAndStart(queryBo);
System.out.println("[OEE][summary][job][controller] job created, resp=" + resp);
return R.ok(resp);
}
/**
* 7 大损失汇总
*/
@@ -77,5 +97,34 @@ public class OeeReportController extends BaseController {
public void exportWord(OeeQueryBo queryBo, HttpServletResponse response) {
oeeReportService.exportWord(queryBo, response);
}
/**
* 理论节拍回归(散点+拟合线)
*
* 说明:该接口用于前端绘制“散点+回归折线”的图形,节拍数据来源于 WMS 回归结果。
*/
@Log(title = "OEE 报表-理论节拍回归", businessType = BusinessType.OTHER)
@GetMapping("/theoryCycle/regression")
public R<Map<String, Object>> theoryCycleRegression(OeeQueryBo queryBo) {
return R.ok(oeeReportService.theoryCycleRegression(queryBo));
}
/**
* 理论节拍回归(异步任务):立即返回 jobId通过 WebSocket 推送进度与结果。
*
* 前端订阅:
* ws://{host}/websocket?type={wsType}
*/
@Log(title = "OEE 报表-理论节拍回归(异步)", businessType = BusinessType.OTHER)
@GetMapping("/theoryCycle/regression/job")
public R<Map<String, Object>> theoryCycleRegressionJob(OeeQueryBo queryBo) {
System.out.println("[OEE][theoryCycle][job][controller] request received, thread=" + Thread.currentThread().getName()
+ ", startTime=" + (queryBo == null ? null : queryBo.getStartTime())
+ ", endTime=" + (queryBo == null ? null : queryBo.getEndTime())
+ ", lineIds=" + (queryBo == null ? null : queryBo.getLineIds()));
Map<String, Object> resp = oeeTheoryCycleJobService.createAndStart(queryBo);
System.out.println("[OEE][theoryCycle][job][controller] job created, resp=" + resp);
return R.ok(resp);
}
}

View File

@@ -43,5 +43,10 @@ public interface IOeeReportService {
* 导出 Word 报表
*/
void exportWord(OeeQueryBo queryBo, HttpServletResponse response);
/**
* 理论节拍线性回归(散点+拟合线),用于前端绘图。
*/
Map<String, Object> theoryCycleRegression(OeeQueryBo queryBo);
}

View File

@@ -0,0 +1,19 @@
package com.klp.da.service;
import com.klp.da.domain.bo.OeeQueryBo;
import java.util.Map;
/**
* OEE summary 异步任务WebSocket 推送)
*/
public interface OeeSummaryJobService {
/**
* 创建任务并异步执行,立即返回 job 信息。
*
* @return Map: { jobId, wsType }
*/
Map<String, Object> createAndStart(OeeQueryBo queryBo);
}

View File

@@ -0,0 +1,19 @@
package com.klp.da.service;
import com.klp.da.domain.bo.OeeQueryBo;
import java.util.Map;
/**
* 理论节拍回归异步任务WebSocket 推送)
*/
public interface OeeTheoryCycleJobService {
/**
* 创建任务并异步执行,立即返回 job 信息。
*
* @return Map: { jobId, wsType }
*/
Map<String, Object> createAndStart(OeeQueryBo queryBo);
}

View File

@@ -0,0 +1,155 @@
package com.klp.da.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.klp.da.domain.bo.OeeQueryBo;
import com.klp.da.domain.vo.OeeLineSummaryVo;
import com.klp.da.service.IOeeReportService;
import com.klp.da.service.OeeSummaryJobService;
import com.klp.framework.websocket.TypeWebSocketUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
@Service
@RequiredArgsConstructor
public class OeeSummaryJobServiceImpl implements OeeSummaryJobService {
public static final String WS_TYPE_PREFIX = "oee_summary:";
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2, r -> {
Thread t = new Thread(r);
t.setName("oee-summary-job");
t.setDaemon(true);
return t;
});
private final IOeeReportService oeeReportService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Map<String, Object> createAndStart(OeeQueryBo queryBo) {
String jobId = UUID.randomUUID().toString().replace("-", "");
String wsType = WS_TYPE_PREFIX + jobId;
String auth = captureAuthorization();
log.info("[OEE][summary][job] created jobId={}, wsType={}, window={}~{}, lines={}, authPresent={}",
jobId,
wsType,
queryBo == null ? null : queryBo.getStartDate(),
queryBo == null ? null : queryBo.getEndDate(),
queryBo == null ? null : queryBo.getLineIds(),
auth != null && !auth.trim().isEmpty());
push(jobId, wsType, "running", 5, "任务已创建,准备生成汇总…", null, null);
OeeQueryBo qb = copyQueryBo(queryBo);
EXECUTOR.submit(() -> run(jobId, wsType, qb, auth));
Map<String, Object> resp = new HashMap<>();
resp.put("jobId", jobId);
resp.put("wsType", wsType);
return resp;
}
private void run(String jobId, String wsType, OeeQueryBo queryBo, String authorization) {
long t0 = System.currentTimeMillis();
try {
System.out.println("[OEE][summary][job] start jobId=" + jobId + ", wsType=" + wsType
+ ", thread=" + Thread.currentThread().getName()
+ ", authPresent=" + (authorization != null && !authorization.trim().isEmpty()));
log.info("[OEE][summary][job] start jobId={}, wsType={}, thread={}, authPresent={}",
jobId, wsType, Thread.currentThread().getName(), authorization != null && !authorization.trim().isEmpty());
push(jobId, wsType, "running", 20, "正在聚合 KPI 与趋势…", null, null);
OeeReportServiceImpl.setAuthOverride(authorization);
List<OeeLineSummaryVo> lines = oeeReportService.summary(queryBo);
push(jobId, wsType, "running", 90, "正在整理汇总结果…", null, null);
Map<String, Object> data = new HashMap<>();
data.put("lines", lines);
push(jobId, wsType, "success", 100, "汇总生成完成", data, null);
System.out.println("[OEE][summary][job] success jobId=" + jobId + ", costMs=" + (System.currentTimeMillis() - t0)
+ ", lineCount=" + (lines == null ? 0 : lines.size()));
log.info("[OEE][summary][job] success jobId={}, costMs={}, lineCount={}",
jobId, (System.currentTimeMillis() - t0), lines == null ? 0 : lines.size());
} catch (Exception e) {
log.warn("summary job failed, jobId={}", jobId, e);
push(jobId, wsType, "failed", 100, "汇总生成失败", null, e.getMessage());
System.out.println("[OEE][summary][job] failed jobId=" + jobId + ", costMs=" + (System.currentTimeMillis() - t0)
+ ", msg=" + e.getMessage());
log.warn("[OEE][summary][job] failed jobId={}, costMs={}, msg={}",
jobId, (System.currentTimeMillis() - t0), e.getMessage());
} finally {
OeeReportServiceImpl.clearAuthOverride();
System.out.println("[OEE][summary][job] end jobId=" + jobId + ", costMs=" + (System.currentTimeMillis() - t0));
log.info("[OEE][summary][job] end jobId={}, costMs={}", jobId, (System.currentTimeMillis() - t0));
}
}
private void push(String jobId, String wsType, String status, int progress, String text,
Map<String, Object> data, String errorMsg) {
try {
Map<String, Object> msg = new HashMap<>();
msg.put("jobId", jobId);
msg.put("status", status);
msg.put("progress", progress);
msg.put("text", text);
if (data != null) {
msg.put("data", data);
}
if (errorMsg != null && !errorMsg.trim().isEmpty()) {
msg.put("errorMsg", errorMsg);
}
TypeWebSocketUtil.sendToType(wsType, objectMapper.writeValueAsString(msg));
} catch (Exception ignore) {
}
}
private OeeQueryBo copyQueryBo(OeeQueryBo queryBo) {
OeeQueryBo qb = new OeeQueryBo();
if (queryBo == null) {
return qb;
}
qb.setStartDate(queryBo.getStartDate());
qb.setEndDate(queryBo.getEndDate());
qb.setLineIds(queryBo.getLineIds());
qb.setStartTime(queryBo.getStartTime());
qb.setEndTime(queryBo.getEndTime());
qb.setTopN(queryBo.getTopN());
qb.setLossCategoryCode(queryBo.getLossCategoryCode());
qb.setKeyword(queryBo.getKeyword());
return qb;
}
private String captureAuthorization() {
try {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs == null || attrs.getRequest() == null) {
return null;
}
HttpServletRequest req = attrs.getRequest();
String auth = req.getHeader("Authorization");
if (auth != null && !auth.trim().isEmpty()) {
return auth.trim();
}
String token = req.getHeader("token");
if (token != null && !token.trim().isEmpty()) {
return "Bearer " + token.trim();
}
} catch (Exception ignore) {
}
return null;
}
}

View File

@@ -0,0 +1,182 @@
package com.klp.da.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.klp.da.domain.bo.OeeQueryBo;
import com.klp.da.service.IOeeReportService;
import com.klp.da.service.OeeTheoryCycleJobService;
import com.klp.framework.websocket.TypeWebSocketUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
@Service
@RequiredArgsConstructor
public class OeeTheoryCycleJobServiceImpl implements OeeTheoryCycleJobService {
/**
* 推送 type 前缀:前端用 ws://host/websocket?type=oee_theory_cycle_regression:{jobId} 订阅。
*/
public static final String WS_TYPE_PREFIX = "oee_theory_cycle_regression:";
private static final long TTL_MS = 10 * 60_000L;
private static final ConcurrentHashMap<String, JobState> JOBS = new ConcurrentHashMap<>();
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2, r -> {
Thread t = new Thread(r);
t.setName("oee-theory-cycle-job");
t.setDaemon(true);
return t;
});
private final IOeeReportService oeeReportService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Map<String, Object> createAndStart(OeeQueryBo queryBo) {
String jobId = UUID.randomUUID().toString().replace("-", "");
String wsType = WS_TYPE_PREFIX + jobId;
JobState st = new JobState();
st.updatedAt = System.currentTimeMillis();
JOBS.put(jobId, st);
String auth = captureAuthorization();
log.info("[OEE][theoryCycle][job] created jobId={}, wsType={}, window={}~{}, lines={}, authPresent={}",
jobId,
wsType,
queryBo == null ? null : queryBo.getStartTime(),
queryBo == null ? null : queryBo.getEndTime(),
queryBo == null ? null : queryBo.getLineIds(),
auth != null && !auth.trim().isEmpty());
// 立即推送“已创建”
push(jobId, wsType, "running", 5, "任务已创建,准备计算…", null, null);
// 异步执行
OeeQueryBo qb = copyQueryBo(queryBo);
EXECUTOR.submit(() -> run(jobId, wsType, qb, auth));
Map<String, Object> resp = new HashMap<>();
resp.put("jobId", jobId);
resp.put("wsType", wsType);
return resp;
}
private void run(String jobId, String wsType, OeeQueryBo queryBo, String authorization) {
long t0 = System.currentTimeMillis();
try {
cleanupExpired();
System.out.println("[OEE][theoryCycle][job] start jobId=" + jobId + ", wsType=" + wsType
+ ", thread=" + Thread.currentThread().getName()
+ ", authPresent=" + (authorization != null && !authorization.trim().isEmpty()));
log.info("[OEE][theoryCycle][job] start jobId={}, wsType={}, thread={}, authPresent={}",
jobId, wsType, Thread.currentThread().getName(), authorization != null && !authorization.trim().isEmpty());
push(jobId, wsType, "running", 20, "开始计算理论节拍回归…", null, null);
// 这里复用现有 WMS 同步回归聚合逻辑(内部会走 WMS 接口)
push(jobId, wsType, "running", 50, "正在拉取样本并拟合回归…", null, null);
OeeReportServiceImpl.setAuthOverride(authorization);
Map<String, Object> data = oeeReportService.theoryCycleRegression(queryBo);
push(jobId, wsType, "running", 90, "正在整理回归结果…", null, null);
JobState st = JOBS.get(jobId);
if (st != null) {
st.updatedAt = System.currentTimeMillis();
}
push(jobId, wsType, "success", 100, "回归计算完成", data, null);
System.out.println("[OEE][theoryCycle][job] success jobId=" + jobId + ", costMs=" + (System.currentTimeMillis() - t0));
log.info("[OEE][theoryCycle][job] success jobId={}, costMs={}", jobId, (System.currentTimeMillis() - t0));
} catch (Exception e) {
log.warn("theoryCycle job failed, jobId={}", jobId, e);
JobState st = JOBS.get(jobId);
if (st != null) {
st.updatedAt = System.currentTimeMillis();
}
push(jobId, wsType, "failed", 100, "回归计算失败", null, e.getMessage());
System.out.println("[OEE][theoryCycle][job] failed jobId=" + jobId + ", costMs=" + (System.currentTimeMillis() - t0)
+ ", msg=" + e.getMessage());
log.warn("[OEE][theoryCycle][job] failed jobId={}, costMs={}, msg={}",
jobId, (System.currentTimeMillis() - t0), e.getMessage());
} finally {
OeeReportServiceImpl.clearAuthOverride();
System.out.println("[OEE][theoryCycle][job] end jobId=" + jobId + ", costMs=" + (System.currentTimeMillis() - t0));
log.info("[OEE][theoryCycle][job] end jobId={}, costMs={}", jobId, (System.currentTimeMillis() - t0));
}
}
private void push(String jobId, String wsType, String status, int progress, String text,
Map<String, Object> data, String errorMsg) {
try {
Map<String, Object> msg = new HashMap<>();
msg.put("jobId", jobId);
msg.put("status", status);
msg.put("progress", progress);
msg.put("text", text);
if (data != null) {
msg.put("data", data);
}
if (errorMsg != null && !errorMsg.trim().isEmpty()) {
msg.put("errorMsg", errorMsg);
}
TypeWebSocketUtil.sendToType(wsType, objectMapper.writeValueAsString(msg));
} catch (Exception ignore) {
}
}
private void cleanupExpired() {
long now = System.currentTimeMillis();
JOBS.entrySet().removeIf(e -> e.getValue() == null || (now - e.getValue().updatedAt) > TTL_MS);
}
private OeeQueryBo copyQueryBo(OeeQueryBo queryBo) {
OeeQueryBo qb = new OeeQueryBo();
if (queryBo == null) {
return qb;
}
qb.setStartDate(queryBo.getStartDate());
qb.setEndDate(queryBo.getEndDate());
qb.setLineIds(queryBo.getLineIds());
qb.setStartTime(queryBo.getStartTime());
qb.setEndTime(queryBo.getEndTime());
qb.setTopN(queryBo.getTopN());
qb.setLossCategoryCode(queryBo.getLossCategoryCode());
qb.setKeyword(queryBo.getKeyword());
return qb;
}
private String captureAuthorization() {
try {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs == null || attrs.getRequest() == null) {
return null;
}
HttpServletRequest req = attrs.getRequest();
String auth = req.getHeader("Authorization");
if (auth != null && !auth.trim().isEmpty()) {
return auth.trim();
}
String token = req.getHeader("token");
if (token != null && !token.trim().isEmpty()) {
return "Bearer " + token.trim();
}
} catch (Exception ignore) {
}
return null;
}
private static class JobState {
long updatedAt;
}
}