|
|
@@ -0,0 +1,301 @@
|
|
|
+package com.mes.laser;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import com.mes.ui.DataUtil;
|
|
|
+
|
|
|
+import java.io.BufferedReader;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
+import java.util.Properties;
|
|
|
+import java.util.Timer;
|
|
|
+import java.util.TimerTask;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
+import java.util.concurrent.Executors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 镭雕整体流程状态机:
|
|
|
+ * 获取镭雕码 -> 回显钢印码 -> 开始镭雕 -> (镭雕软件异步)等待完成上报
|
|
|
+ * -> 生成客户编码并绑定 -> 覆盖回显为客户编码 -> 按op040逻辑提交建档 -> 打印流转卡 -> 解锁
|
|
|
+ *
|
|
|
+ * 从点击获取镭雕码到收到完成信号之间,按钮必须锁死,不允许重复触发。
|
|
|
+ * @author hzd
|
|
|
+ */
|
|
|
+public class LaserFlowManager {
|
|
|
+
|
|
|
+ public interface UiCallback {
|
|
|
+ void onStatus(String msg, boolean error);
|
|
|
+ void onLockButton(boolean lock);
|
|
|
+ // 把code内容显示到扫码框:先显示钢印码,完成后覆盖显示客户编码
|
|
|
+ void onCodeReceived(String code);
|
|
|
+ // 按op040既有逻辑提交建档(复用finish_ok_bt里的sendCreate),返回是否提交成功
|
|
|
+ boolean onSubmitResult(String customerSn, String qret);
|
|
|
+ }
|
|
|
+
|
|
|
+ private final UiCallback uiCallback;
|
|
|
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
|
|
+ private LaserCompleteServer completeServer;
|
|
|
+
|
|
|
+ // 当前进行中的请求标识和对应的待镭雕码,用于校验完成上报是否对应本次请求,
|
|
|
+ // 也用于"模拟完成"调试按钮直接复用(真实回调则由镭雕软件POST传入,不依赖这两个字段)
|
|
|
+ private volatile String currentCodeId = null;
|
|
|
+ private volatile String currentCode = null;
|
|
|
+ private volatile boolean waitingComplete = false;
|
|
|
+ private Timer completeTimeoutTimer;
|
|
|
+
|
|
|
+ private String laserServerUrl;
|
|
|
+ private String getCodePath = "/laser/getCode";
|
|
|
+ private String startPath = "/laser/start";
|
|
|
+ private int laserTimeout = 15000;
|
|
|
+ private int localPort = 9002;
|
|
|
+ private int completeTimeoutMs = 60000;
|
|
|
+ private String prodCode = "T09";
|
|
|
+ private String printerName = "";
|
|
|
+
|
|
|
+ public LaserFlowManager(UiCallback uiCallback) {
|
|
|
+ this.uiCallback = uiCallback;
|
|
|
+ loadConfig();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void loadConfig() {
|
|
|
+ try {
|
|
|
+ InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
|
|
|
+ Properties pro = new Properties();
|
|
|
+ BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
|
|
|
+ pro.load(br);
|
|
|
+ laserServerUrl = pro.getProperty("mes.laser.server_url", "").trim();
|
|
|
+ laserTimeout = Integer.parseInt(pro.getProperty("mes.laser.timeout", "15000").trim());
|
|
|
+ localPort = Integer.parseInt(pro.getProperty("mes.laser.local_port", "9002").trim());
|
|
|
+ completeTimeoutMs = Integer.parseInt(pro.getProperty("mes.laser.complete_timeout", "60000").trim());
|
|
|
+ prodCode = pro.getProperty("mes.prod_code", "T09").trim();
|
|
|
+ printerName = pro.getProperty("mes.laser.printer_name", "").trim();
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 启动本地HTTP服务,接收镭雕软件的完成上报(应用启动时调用一次)
|
|
|
+ */
|
|
|
+ public void startCompleteServer() {
|
|
|
+ completeServer = new LaserCompleteServer(this::handleComplete);
|
|
|
+ completeServer.start(localPort);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void stopCompleteServer() {
|
|
|
+ if (completeServer != null) {
|
|
|
+ completeServer.stop();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 点击"获取镭雕码"按钮触发的入口
|
|
|
+ * @param oprno 工位号
|
|
|
+ * @param lineSn 产线编号
|
|
|
+ */
|
|
|
+ public void startLaserFlow(String oprno, String lineSn) {
|
|
|
+ if (waitingComplete) {
|
|
|
+ uiCallback.onStatus("镭雕进行中,请等待完成", true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ uiCallback.onLockButton(true);
|
|
|
+ uiCallback.onStatus("正在获取镭雕码...", false);
|
|
|
+
|
|
|
+ executor.submit(() -> {
|
|
|
+ JSONObject codeData = LaserClient.getCode(laserServerUrl, getCodePath, lineSn, oprno, laserTimeout);
|
|
|
+ if (codeData == null) {
|
|
|
+ uiCallback.onStatus("获取镭雕码失败,请重试", true);
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String codeId = codeData.getString("codeId");
|
|
|
+ String code = codeData.getString("code");
|
|
|
+ if (codeId == null || code == null) {
|
|
|
+ uiCallback.onStatus("获取镭雕码返回数据不完整", true);
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 回显钢印码到扫码框
|
|
|
+ uiCallback.onCodeReceived(code);
|
|
|
+ uiCallback.onStatus("已获取镭雕码,正在发送开始信号...", false);
|
|
|
+
|
|
|
+ boolean startOk = false;
|
|
|
+ // 接口2失败重试2-3次
|
|
|
+ for (int i = 0; i < 3 && !startOk; i++) {
|
|
|
+ startOk = LaserClient.start(laserServerUrl, startPath, codeId, code, laserTimeout);
|
|
|
+ if (!startOk && i < 2) {
|
|
|
+ try { Thread.sleep(500); } catch (InterruptedException ignored) {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!startOk) {
|
|
|
+ uiCallback.onStatus("发送开始镭雕信号失败,请人工确认设备状态后重试", true);
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 进入等待完成状态
|
|
|
+ currentCodeId = codeId;
|
|
|
+ currentCode = code;
|
|
|
+ waitingComplete = true;
|
|
|
+ uiCallback.onStatus("镭雕中,请等待完成信号...", false);
|
|
|
+ startCompleteTimeoutTimer(codeId);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 完成信号超时定时器:超时后视为该codeId废弃,允许人工复位解锁
|
|
|
+ private void startCompleteTimeoutTimer(String codeId) {
|
|
|
+ if (completeTimeoutTimer != null) {
|
|
|
+ completeTimeoutTimer.cancel();
|
|
|
+ }
|
|
|
+ completeTimeoutTimer = new Timer();
|
|
|
+ completeTimeoutTimer.schedule(new TimerTask() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ if (waitingComplete && codeId.equals(currentCodeId)) {
|
|
|
+ uiCallback.onStatus("镭雕完成信号超时,可点击复位后重试", true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, completeTimeoutMs);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 人工复位:超时或异常时,操作员手动点击解锁按钮
|
|
|
+ */
|
|
|
+ public void manualReset() {
|
|
|
+ currentCodeId = null;
|
|
|
+ currentCode = null;
|
|
|
+ waitingComplete = false;
|
|
|
+ if (completeTimeoutTimer != null) {
|
|
|
+ completeTimeoutTimer.cancel();
|
|
|
+ }
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ uiCallback.onStatus("已复位,可重新获取镭雕码", false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调试用:模拟"获取镭雕码+开始镭雕"整个前半段,完全绕开真实HTTP请求。
|
|
|
+ * 供应商接口未接通前,用于本地走通整体流程:直接进入"镭雕中"状态,
|
|
|
+ * 之后可以点"模拟完成信号"按钮继续走完后半段。
|
|
|
+ * @param steelSn 模拟的钢印码,传空则自动生成一个测试用码
|
|
|
+ */
|
|
|
+ public void simulateGetCode(String steelSn) {
|
|
|
+ if (waitingComplete) {
|
|
|
+ uiCallback.onStatus("镭雕进行中,请等待完成或先复位", true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String code = (steelSn == null || steelSn.trim().isEmpty())
|
|
|
+ ? "JS" + System.currentTimeMillis() % 1000000000L
|
|
|
+ : steelSn.trim();
|
|
|
+ String codeId = UUID.randomUUID().toString();
|
|
|
+
|
|
|
+ uiCallback.onCodeReceived(code);
|
|
|
+ uiCallback.onLockButton(true);
|
|
|
+
|
|
|
+ currentCodeId = codeId;
|
|
|
+ currentCode = code;
|
|
|
+ waitingComplete = true;
|
|
|
+ uiCallback.onStatus("[调试]已模拟获取镭雕码并发送开始信号,镭雕中,可点模拟完成信号", false);
|
|
|
+ startCompleteTimeoutTimer(codeId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调试用:模拟镭雕软件的完成回调(供应商真实接口未接通前,用于走通整体流程)。
|
|
|
+ * 使用当前进行中的codeId/code,等效于镭雕软件真实POST了完成上报。
|
|
|
+ * @param result OK/NG
|
|
|
+ * @param errorMsg 失败原因(result=NG时使用)
|
|
|
+ */
|
|
|
+ public void simulateComplete(String result, String errorMsg) {
|
|
|
+ if (!waitingComplete || currentCodeId == null) {
|
|
|
+ uiCallback.onStatus("当前没有进行中的镭雕请求,无法模拟完成信号", true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ System.out.println("[镭雕流程-调试] 模拟完成信号:codeId=" + currentCodeId + " code=" + currentCode + " result=" + result);
|
|
|
+ handleComplete(currentCodeId, currentCode, result, errorMsg);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调试用:单独测试"生成客户编码"接口,不依赖整体流程状态,不做提交/打印。
|
|
|
+ * @param steelSn 测试用钢印码
|
|
|
+ */
|
|
|
+ public void manualGenCustomerSn(String steelSn) {
|
|
|
+ if (steelSn == null || steelSn.trim().isEmpty()) {
|
|
|
+ uiCallback.onStatus("请输入测试用钢印码", true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ uiCallback.onStatus("正在测试生成客户编码...", false);
|
|
|
+ executor.submit(() -> {
|
|
|
+ JSONObject genResp = DataUtil.genCustomerSn(prodCode, steelSn.trim());
|
|
|
+ if (genResp == null || genResp.get("result") == null
|
|
|
+ || !"true".equalsIgnoreCase(genResp.get("result").toString())) {
|
|
|
+ String msg = genResp != null && genResp.get("message") != null
|
|
|
+ ? genResp.get("message").toString() : "生成客户编码失败";
|
|
|
+ uiCallback.onStatus("测试失败:" + msg, true);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String customerSn = genResp.get("data") == null ? "" : genResp.get("data").toString();
|
|
|
+ uiCallback.onCodeReceived(customerSn);
|
|
|
+ uiCallback.onStatus("测试生成客户编码成功:" + customerSn, false);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 镭雕软件调用接口3上报完成结果的回调(真实HTTP回调 或 模拟按钮 都走这里)
|
|
|
+ private void handleComplete(String codeId, String steelSn, String result, String errorMsg) {
|
|
|
+ if (completeTimeoutTimer != null) {
|
|
|
+ completeTimeoutTimer.cancel();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!waitingComplete || codeId == null || !codeId.equals(currentCodeId)) {
|
|
|
+ // 迟到的旧回调或未处于等待状态,直接忽略(该codeId已被判定废弃或未曾发起)
|
|
|
+ System.out.println("[镭雕流程] 忽略无效/迟到的完成上报,codeId=" + codeId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ waitingComplete = false;
|
|
|
+ currentCodeId = null;
|
|
|
+ currentCode = null;
|
|
|
+
|
|
|
+ if (!"OK".equalsIgnoreCase(result)) {
|
|
|
+ uiCallback.onStatus("镭雕失败:" + (errorMsg == null ? "" : errorMsg), true);
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ uiCallback.onStatus("镭雕完成,正在生成客户编码...", false);
|
|
|
+
|
|
|
+ executor.submit(() -> {
|
|
|
+ JSONObject genResp = DataUtil.genCustomerSn(prodCode, steelSn);
|
|
|
+ if (genResp == null || genResp.get("result") == null
|
|
|
+ || !"true".equalsIgnoreCase(genResp.get("result").toString())) {
|
|
|
+ String msg = genResp != null && genResp.get("message") != null
|
|
|
+ ? genResp.get("message").toString() : "生成客户编码失败";
|
|
|
+ uiCallback.onStatus(msg, true);
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String customerSn = genResp.get("data") == null ? "" : genResp.get("data").toString();
|
|
|
+ // 覆盖回显为客户编码,随后按此编码提交建档
|
|
|
+ uiCallback.onCodeReceived(customerSn);
|
|
|
+
|
|
|
+ boolean submitOk = uiCallback.onSubmitResult(customerSn, "OK");
|
|
|
+ if (!submitOk) {
|
|
|
+ uiCallback.onStatus("客户编码生成成功,但提交MES建档失败:" + customerSn, true);
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean printOk = LaserCardPrintUtil.printCard(printerName, steelSn, customerSn);
|
|
|
+ if (!printOk) {
|
|
|
+ uiCallback.onStatus("已提交建档,但流转卡打印失败:" + customerSn, true);
|
|
|
+ } else {
|
|
|
+ uiCallback.onStatus("流转卡打印完成,可继续下一件", false);
|
|
|
+ }
|
|
|
+
|
|
|
+ uiCallback.onLockButton(false);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+}
|