From 35e67e2455e56d8b315a7c0e55ea8ba2b3441320 Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Fri, 7 Aug 2026 09:56:12 +0800 Subject: [PATCH] feat(javaTest): model idempotent floor transfer jobs --- .../history/simulation/AssignmentMode.java | 11 +++ .../simulation/FloorTransferGateway.java | 79 ++++++++++++++++ .../history/simulation/FloorTransferJob.java | 88 ++++++++++++++++++ .../simulation/FloorTransferStatus.java | 17 ++++ .../simulation/FloorTransferTopology.java | 44 +++++++++ .../SimulatedFloorTransferGateway.java | 89 ++++++++++++++++++ .../SimulatedFloorTransferGatewayTest.java | 91 +++++++++++++++++++ 7 files changed, 419 insertions(+) create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/AssignmentMode.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferGateway.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferJob.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferStatus.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferTopology.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/SimulatedFloorTransferGateway.java create mode 100644 javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/SimulatedFloorTransferGatewayTest.java diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/AssignmentMode.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/AssignmentMode.java new file mode 100644 index 0000000..4cf6f2d --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/AssignmentMode.java @@ -0,0 +1,11 @@ +package com.ft546.wcs.shadow.history.simulation; + +/** + * 表示中心调度选车后,车辆进入业务任务前需要采用的编排方式。 + */ +public enum AssignmentMode { + /** 车辆与任务源点同层,可以直接执行接近路径和服务路径。 */ + DIRECT, + /** 车辆需要先由现有 WCS 完成换层,再从实际出口重新规划。 */ + FLOOR_TRANSFER +} diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferGateway.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferGateway.java new file mode 100644 index 0000000..2c03551 --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferGateway.java @@ -0,0 +1,79 @@ +package com.ft546.wcs.shadow.history.simulation; + +import java.util.Optional; + +/** + * 中心调度创建和查询换层作业的出站端口。 + * + *
提交成功只代表现有 WCS 接受了作业,不代表车辆已经换层。中心必须继续消费状态,直到
+ * {@link FloorTransferStatus#COMPLETED} 携带实际出口事实后,才能更新车辆位置并规划父任务。
+ */
+public interface FloorTransferGateway {
+ /** 幂等提交一个换层作业;设备忙时返回 BUSY,调用方保持父任务等待。 */
+ Submission submit(FloorTransferJob job);
+
+ /**
+ * 推进或轮询指定作业的最新事实。生产适配器实现应查询持久化队列和设备快照,模拟器实现按 tick 推进。
+ */
+ TransferSnapshot advance(String idempotencyKey);
+
+ /** 只读获取当前事实,不产生状态迁移,用于重启恢复和重复回调对账。 */
+ TransferSnapshot snapshot(String idempotencyKey);
+
+ enum SubmissionCode {
+ CREATED,
+ EXISTING,
+ BUSY,
+ REJECTED
+ }
+
+ /** 换层提交的业务结果;BUSY/REJECTED 不携带已创建作业。 */
+ final class Submission {
+ private final SubmissionCode code;
+ private final FloorTransferJob job;
+ private final String reason;
+
+ public Submission(SubmissionCode code, FloorTransferJob job, String reason) {
+ if (code == null) throw new IllegalArgumentException("submission code is required");
+ this.code = code;
+ this.job = job;
+ this.reason = reason == null ? "" : reason;
+ }
+
+ public SubmissionCode getCode() { return code; }
+ public Optional 该对象只描述“哪辆车因哪个父任务从哪层到哪层”,不包含 PLC 地址、寄存器或提升机
+ * 握手细节。生产适配器可以把它映射为 {@code tsj_device_execution_queue},离线仿真则交给
+ * {@link SimulatedFloorTransferGateway} 执行相同契约。
+ */
+public final class FloorTransferJob {
+ private final String idempotencyKey;
+ private final String parentTaskId;
+ private final String vehicleId;
+ private final int sourceFloor;
+ private final int targetFloor;
+ private final String sourceSite;
+ private final String targetSite;
+ private final int priority;
+
+ private FloorTransferJob(String idempotencyKey, String parentTaskId,
+ String vehicleId, int sourceFloor, int targetFloor,
+ String sourceSite, String targetSite, int priority) {
+ this.idempotencyKey = requireText(idempotencyKey, "idempotency key");
+ this.parentTaskId = requireText(parentTaskId, "parent task id");
+ this.vehicleId = requireText(vehicleId, "vehicle id");
+ if (sourceFloor <= 0 || targetFloor <= 0 || sourceFloor == targetFloor) {
+ throw new IllegalArgumentException("floor transfer requires two different positive floors");
+ }
+ this.sourceFloor = sourceFloor;
+ this.targetFloor = targetFloor;
+ this.sourceSite = requireText(sourceSite, "source transfer site");
+ this.targetSite = requireText(targetSite, "target transfer site");
+ this.priority = priority;
+ }
+
+ /**
+ * 用稳定业务字段创建换层命令;相同父任务和车辆重试时会得到同一个幂等键。
+ */
+ public static FloorTransferJob create(String parentTaskId, String vehicleId,
+ int sourceFloor, int targetFloor,
+ String sourceSite, String targetSite,
+ int priority) {
+ String task = requireText(parentTaskId, "parent task id");
+ String vehicle = requireText(vehicleId, "vehicle id");
+ String key = task + "|" + vehicle + "|" + sourceFloor + "->" + targetFloor;
+ return new FloorTransferJob(key, task, vehicle, sourceFloor, targetFloor,
+ sourceSite, targetSite, priority);
+ }
+
+ public String getIdempotencyKey() { return idempotencyKey; }
+ public String getParentTaskId() { return parentTaskId; }
+ public String getVehicleId() { return vehicleId; }
+ public int getSourceFloor() { return sourceFloor; }
+ public int getTargetFloor() { return targetFloor; }
+ public String getSourceSite() { return sourceSite; }
+ public String getTargetSite() { return targetSite; }
+ public int getPriority() { return priority; }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (!(other instanceof FloorTransferJob)) return false;
+ FloorTransferJob job = (FloorTransferJob) other;
+ return idempotencyKey.equals(job.idempotencyKey)
+ && parentTaskId.equals(job.parentTaskId)
+ && vehicleId.equals(job.vehicleId)
+ && sourceFloor == job.sourceFloor
+ && targetFloor == job.targetFloor
+ && sourceSite.equals(job.sourceSite)
+ && targetSite.equals(job.targetSite)
+ && priority == job.priority;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(idempotencyKey, parentTaskId, vehicleId, sourceFloor,
+ targetFloor, sourceSite, targetSite, priority);
+ }
+
+ private static String requireText(String value, String name) {
+ if (value == null || value.trim().isEmpty()) {
+ throw new IllegalArgumentException(name + " must not be blank");
+ }
+ return value;
+ }
+}
diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferStatus.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferStatus.java
new file mode 100644
index 0000000..74ae204
--- /dev/null
+++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferStatus.java
@@ -0,0 +1,17 @@
+package com.ft546.wcs.shadow.history.simulation;
+
+/**
+ * 换层作业在中心调度与设备适配器之间使用的标准状态。
+ */
+public enum FloorTransferStatus {
+ /** 作业已经持久化或被适配器接受,但设备尚未报告开始。 */
+ SUBMITTED,
+ /** 现有 WCS 已经开始执行车辆换层。 */
+ RUNNING,
+ /** 完成事实已经确认车辆所在楼层和实际出口。 */
+ COMPLETED,
+ /** 适配器明确证明作业未开始并拒绝执行。 */
+ REJECTED,
+ /** 作业可能已执行但结果无法确认,必须保留车辆锁等待对账。 */
+ UNKNOWN
+}
diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferTopology.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferTopology.java
new file mode 100644
index 0000000..7d585a1
--- /dev/null
+++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/FloorTransferTopology.java
@@ -0,0 +1,44 @@
+package com.ft546.wcs.shadow.history.simulation;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 保存项目层到提升机交接站点的映射,明确把换层端口与普通地图边分离。
+ */
+public final class FloorTransferTopology {
+ private final Map 它实现与生产适配器相同的幂等提交契约,但不连接 SQL Server 或 PLC。换层不是地图移动,
+ * 因此该网关只在完成事实中返回实际出口,车辆位置由中心编排器消费事实后统一提交。
+ */
+public final class SimulatedFloorTransferGateway implements FloorTransferGateway {
+ private final Map