cockpit-work-order-list.patch 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. diff --git a/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java b/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java
  2. index f85f8df3..8464ba77 100644
  3. --- a/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java
  4. +++ b/pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/cockpit/CockpitWorkOrderReadController.java
  5. @@ -4,6 +4,7 @@ import com.zksy.cockpit.foundation.CockpitDeviceNotFoundException;
  6. import com.zksy.cockpit.foundation.CockpitErrorPayload;
  7. import com.zksy.cockpit.foundation.CockpitInvalidRequestException;
  8. import com.zksy.cockpit.foundation.CockpitReadException;
  9. +import com.zksy.cockpit.workorder.CockpitWorkOrderListResponse;
  10. import com.zksy.cockpit.workorder.CockpitWorkOrderReadResponse;
  11. import com.zksy.cockpit.workorder.CockpitWorkOrderReadService;
  12. import com.zksy.cockpit.workorder.CockpitWorkOrderTimelineResponse;
  13. @@ -16,6 +17,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
  14. import org.springframework.web.bind.annotation.GetMapping;
  15. import org.springframework.web.bind.annotation.PathVariable;
  16. import org.springframework.web.bind.annotation.RequestHeader;
  17. +import org.springframework.web.bind.annotation.RequestParam;
  18. import org.springframework.web.bind.annotation.RestController;
  19. import org.springframework.web.util.UriUtils;
  20. @@ -62,6 +64,31 @@ public class CockpitWorkOrderReadController {
  21. return ResponseEntity.ok(AjaxResult.success(response));
  22. }
  23. + /**
  24. + * 全量工单分页(票 65:报警效率面板的「当前工单列表」)。
  25. + *
  26. + * <p>为什么单开一个路径而不是复用 `/equipments/{code}/work-orders`:报警族的工单跨设备,
  27. + * 面板要的是「当前所有工单」;实测该设备维度能查到 7 张工单,而
  28. + * `handling-efficiency` 的 linkedWorkOrderCount=0 —— 两者粒度不同,不能互相代替。
  29. + *
  30. + * <p>路径挂在 `/api/cockpit/v1/**` 下:免鉴权白名单只按**路径前缀**放行
  31. + * (`SecurityConfig: antMatchers(GET, "/api/cockpit/v1/**").permitAll()`),
  32. + * 运营侧 `/api/ops/**` 需要登录态,驾驶舱不能直接用。
  33. + *
  34. + * <p>三个查询参数都可缺省:`page` 默认 1、`pageSize` 默认 10(上限 100)、
  35. + * `stateCode` 缺省不筛。夹取在 service,控制器不做第二套校验。
  36. + */
  37. + @GetMapping("/api/cockpit/v1/work-orders")
  38. + public ResponseEntity<AjaxResult> workOrderList(
  39. + @RequestParam(value = "page", required = false) Integer page,
  40. + @RequestParam(value = "pageSize", required = false) Integer pageSize,
  41. + @RequestParam(value = "stateCode", required = false) Integer stateCode,
  42. + @RequestHeader(value = REQUEST_ID_HEADER, required = false) String requestId) {
  43. + CockpitWorkOrderListResponse response = workOrderService.loadAll(
  44. + resolveRequestId(requestId), page, pageSize, stateCode);
  45. + return ResponseEntity.ok(AjaxResult.success(response));
  46. + }
  47. +
  48. @ExceptionHandler(CockpitInvalidRequestException.class)
  49. public ResponseEntity<AjaxResult> handleInvalidRequest(HttpServletRequest request) {
  50. return error(HttpStatus.BAD_REQUEST, workOrderService.invalidRequest(
  51. diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java
  52. index dc285df6..ecb14d4c 100644
  53. --- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java
  54. +++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java
  55. @@ -76,6 +76,45 @@ public class BusinessCockpitWorkOrderReadAdapter implements CockpitWorkOrderRead
  56. .collect(Collectors.toList());
  57. }
  58. + /**
  59. + * 全量工单分页(票 65:报警效率面板的「当前工单列表」)。
  60. + *
  61. + * <p>与设备维度同一条读取路径,只是**不加 device_id 约束**:报警族的工单在库里
  62. + * 本就跨设备存在(实测该端点 24 条报警事件在 handling-efficiency 里
  63. + * linkedWorkOrderCount=0,但设备维度能查到 7 张工单),所以「当前工单」只能是全量口径。
  64. + *
  65. + * <p>三个刻意的取舍:
  66. + * <ul>
  67. + * <li>{@code total} 走 {@code count(wrapper)} 独立查询,不由本页条数反推(票 61 纪律);</li>
  68. + * <li>排序与设备维度一致(创建时间倒序)——两个入口返回同一张表时顺序必须相同;</li>
  69. + * <li>关联解析仍走 {@link #toAlarmSummary}:全量列表里「是否关联报警」逐张如实标,
  70. + * 不因为换了个查询入口就一律标 true/false。</li>
  71. + * </ul>
  72. + */
  73. + @Override
  74. + public CockpitWorkOrderListSnapshot loadAll(Integer stateCode, int limit, int offset) {
  75. + LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
  76. + if (stateCode != null) {
  77. + wrapper.eq(WorkOrder::getOrderStatus, stateCode);
  78. + }
  79. + long total = workOrderService.count(wrapper);
  80. + List<WorkOrder> orders = total <= offset
  81. + ? Collections.emptyList()
  82. + : workOrderService.list(wrapper
  83. + .orderByDesc(WorkOrder::getCreateTime)
  84. + .last("LIMIT " + limit + " OFFSET " + offset));
  85. + // 空页时不再打一次 association 查询:alarmsByOrderId 对空集合本来就早退,但显式一点更省一次调用栈。
  86. + List<CockpitWorkOrderSummary> summaries = orders.isEmpty()
  87. + ? Collections.emptyList()
  88. + : toAlarmSummaries(orders);
  89. + return new CockpitWorkOrderListSnapshot(
  90. + summaries,
  91. + (int) total,
  92. + limit <= 0 ? 1 : (offset / limit) + 1,
  93. + limit,
  94. + stateCode);
  95. + }
  96. +
  97. /**
  98. * 按对外报警标识解析报警:先按 {@code alarm_no}(真实单号),再退回主键 {@code id}。
  99. *
  100. diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java
  101. index b461d3f2..3df39ccc 100644
  102. --- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java
  103. +++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadAdapter.java
  104. @@ -6,4 +6,17 @@ public interface CockpitWorkOrderReadAdapter {
  105. CockpitWorkOrderReadSnapshot loadByEvent(String eventSource, String eventCode);
  106. CockpitWorkOrderTimelineSnapshot loadTimeline(String workOrderCode);
  107. -}
  108. \ No newline at end of file
  109. +
  110. + /**
  111. + * 全量工单分页(票 65:报警效率面板要列「当前工单列表」,不按设备也不按事件筛)。
  112. + *
  113. + * <p>与设备维度共用同一条读取路径(运营侧 {@code WorkOrderService})与同一份
  114. + * {@link CockpitWorkOrderSummary} 映射,区别只在不加 {@code device_id} 约束。
  115. + * 排序口径与设备维度一致(创建时间倒序),前端才能稳定翻页。
  116. + *
  117. + * @param stateCode 工单状态码 0–7;{@code null} 表示不筛
  118. + * @param limit 本页条数(调用方已夹到合法区间)
  119. + * @param offset 偏移量,{@code (page-1)*limit}
  120. + */
  121. + CockpitWorkOrderListSnapshot loadAll(Integer stateCode, int limit, int offset);
  122. +}
  123. diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java
  124. index e898dba3..40d7acd4 100644
  125. --- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java
  126. +++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/CockpitWorkOrderReadService.java
  127. @@ -18,6 +18,12 @@ import java.util.List;
  128. public class CockpitWorkOrderReadService {
  129. private static final List<String> VALID_EVENT_SOURCES = Arrays.asList("ALARM", "WARNING");
  130. + /** 全量工单列表的默认页大小(与驾驶舱其它列表端点一致)。 */
  131. + private static final int DEFAULT_PAGE_SIZE = 10;
  132. +
  133. + /** 单次最多取多少张:防一个 pageSize 把全库拉下来。 */
  134. + private static final int MAX_PAGE_SIZE = 100;
  135. +
  136. private final CockpitWorkOrderReadAdapter adapter;
  137. private final Clock clock;
  138. @@ -64,6 +70,44 @@ public class CockpitWorkOrderReadService {
  139. return error(requestId, CockpitErrorCategory.INVALID_REQUEST, "工单查询参数不合法");
  140. }
  141. + /**
  142. + * 全量工单分页(票 65:报警效率面板的「当前工单列表」,不按设备也不按事件)。
  143. + *
  144. + * <p>翻页参数在这里**夹到合法区间**,不信任入参直接下到 SQL 的 LIMIT/OFFSET:
  145. + * 非法页码轻则返回错页、重则合成语法错误的 SQL。夹取后回显实际生效值,
  146. + * 前端据此纠偏(与票 61「越界页码夹回」同一条纪律,只是这次发生在服务端入口)。
  147. + *
  148. + * @param page 页码 1 起(null/越界 → 1)
  149. + * @param pageSize 每页条数(null/越界 → 10;上限 100,避免一次拉全库)
  150. + * @param stateCode 工单状态码 0–7(null 或越界 → 不筛)
  151. + */
  152. + public CockpitWorkOrderListResponse loadAll(
  153. + String requestId,
  154. + Integer page,
  155. + Integer pageSize,
  156. + Integer stateCode) {
  157. + int safePage = page == null || page < 1 ? 1 : page;
  158. + int safeSize = pageSize == null || pageSize < 1 ? DEFAULT_PAGE_SIZE : Math.min(pageSize, MAX_PAGE_SIZE);
  159. + Integer safeState = stateCode != null && stateCode >= 0 && stateCode <= 7 ? stateCode : null;
  160. + CockpitWorkOrderListSnapshot snapshot = adapter.loadAll(
  161. + safeState, safeSize, (safePage - 1) * safeSize);
  162. + if (snapshot == null) {
  163. + throw new CockpitReadException("work order list unavailable");
  164. + }
  165. + List<CockpitWorkOrderSummary> workOrders = snapshot.getWorkOrders() == null
  166. + ? new ArrayList<>() : snapshot.getWorkOrders();
  167. + boolean empty = workOrders.isEmpty();
  168. + return new CockpitWorkOrderListResponse(
  169. + requestId,
  170. + "v1",
  171. + true,
  172. + empty ? "EMPTY" : "READY",
  173. + empty ? "NO_WORK_ORDERS" : "READY",
  174. + CockpitWorkOrderStateDictionary.states(),
  175. + workOrders,
  176. + new CockpitWorkOrderListResponse.Pagination(safePage, safeSize, snapshot.getTotal()));
  177. + }
  178. +
  179. public CockpitErrorPayload notFound(String requestId) {
  180. return error(requestId, CockpitErrorCategory.NOT_FOUND, "工单或关联对象不存在");
  181. }
  182. diff --git a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java
  183. index 7f8a694d..85ef228a 100644
  184. --- a/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java
  185. +++ b/pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/EmptyCockpitWorkOrderReadAdapter.java
  186. @@ -18,4 +18,9 @@ public class EmptyCockpitWorkOrderReadAdapter implements CockpitWorkOrderReadAda
  187. public CockpitWorkOrderTimelineSnapshot loadTimeline(String workOrderCode) {
  188. return null;
  189. }
  190. +
  191. + @Override
  192. + public CockpitWorkOrderListSnapshot loadAll(Integer stateCode, int limit, int offset) {
  193. + return null;
  194. + }
  195. }
  196. \ No newline at end of file