diff --git a/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java b/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java index f301899e7..e1d2a359b 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java +++ b/cf-module-prod-executor/cf-module-prod-executor-api/src/main/java/com/cf/imes/module/executor/enums/ErrorCodeConstants.java @@ -24,6 +24,7 @@ public class ErrorCodeConstants { public static final ErrorCode ORDER_PLATE_UPDATE_CONCURRENCY_ERROR = new ErrorCode(1_003_107_012, "order.plate.update.concurrency.error"); public static final ErrorCode GOODS_PLATE_UPDATE_CONCURRENCY_ERROR = new ErrorCode(1_003_107_013, "goods.plate.update.concurrency.error"); public static final ErrorCode BODY_PLATE_UPDATE_CONCURRENCY_ERROR = new ErrorCode(1_003_107_014, "body.plate.update.concurrency.error"); + public static final ErrorCode PRODUCING_PLAN_ORDER_SYNC_FAILED = new ErrorCode(1_003_107_016, "producing.plan.order.sync.failed"); public static final ErrorCode ORDER_PLAN_LOCKED = new ErrorCode(1_003_107_015, "当前排单已锁定,请稍后再试"); diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plan/PlanDO.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plan/PlanDO.java index 157f654ea..10575105a 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plan/PlanDO.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/dal/dataobject/plan/PlanDO.java @@ -152,4 +152,9 @@ public class PlanDO extends BaseDO { * 已开板件数量 */ private Integer cuttedPlateNum; -} \ No newline at end of file + + /** + * 组件生产系统返回的生产排单 ID。 + */ + private Long producingPlanOrderId; +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/rpc/config/RpcConfiguration.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/rpc/config/RpcConfiguration.java index 535a9d654..a7bcc95ff 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/rpc/config/RpcConfiguration.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/rpc/config/RpcConfiguration.java @@ -2,6 +2,7 @@ package com.cf.imes.module.executor.framework.rpc.config; import com.cf.imes.module.infra.api.file.FileApi; import com.cf.imes.module.system.api.application.ApplicationApi; +import com.cf.imes.module.system.api.auth.AuthApi; import com.cf.imes.module.system.api.assemblyconfig.AssemblyConfigApi; import com.cf.imes.module.system.api.customplateno.CustomPlateNoSeqApi; import com.cf.imes.module.system.api.dataSource.DataSourceApi; @@ -20,6 +21,7 @@ import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) @EnableFeignClients(clients = {AdminUserApi.class, MachineApi.class, DictDataApi.class, FileApi.class, FileApi.class, DataSourceApi.class, ApplicationApi.class, - OrganApi.class, SystemConfigApi.class, CustomPlateNoSeqApi.class, PermissionApi.class, AssemblyConfigApi.class}) + OrganApi.class, SystemConfigApi.class, CustomPlateNoSeqApi.class, PermissionApi.class, + AssemblyConfigApi.class, AuthApi.class}) public class RpcConfiguration { } diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/PlanServiceImpl.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/PlanServiceImpl.java index ea9c3bedd..73045abe3 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/PlanServiceImpl.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/PlanServiceImpl.java @@ -58,6 +58,8 @@ import com.cf.imes.module.executor.dal.mysql.remainplaten.RemainPlateMapper; import com.cf.imes.module.executor.enums.ErrorCodeConstants; import com.cf.imes.module.executor.enums.OrderPlanQueryTypeEnum; import com.cf.imes.module.executor.enums.OrderStatusEnum; +import com.cf.imes.module.executor.service.plan.producing.ProducingPlanOrderClient; +import com.cf.imes.module.system.api.auth.AuthApi; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.aop.framework.AopContext; @@ -131,6 +133,12 @@ public class PlanServiceImpl implements PlanService { @Resource private StringRedisTemplate stringRedisTemplate; + @Resource + private AuthApi authApi; + + @Resource + private ProducingPlanOrderClient producingPlanOrderClient; + // 重复使用的常量 private static final String FIELD_ORDER_ID = "o.id"; private static final String FIELD_ORDER_TYPE = "o.order_type"; @@ -203,6 +211,7 @@ public class PlanServiceImpl implements PlanService { } // 增加排单 planMapper.insertBatch(planDOS); + createProducingPlanOrders(planDOS); } } @@ -319,6 +328,7 @@ public class PlanServiceImpl implements PlanService { .in(OrderItemDO::getPlateId, plateIdList)); } + deleteProducingPlanOrders(planDOS); } /** @@ -1291,7 +1301,7 @@ public class PlanServiceImpl implements PlanService { List deletePlanIds = deleteOrderPlateIds.stream().map(OrderPlateIds::getPlanId).distinct().toList(); - validatePlanExists(deletePlanIds); + List deletePlans = validatePlanExists(deletePlanIds); List plateStatus = Arrays.asList(OrderPlateCutStatusEnum.OPENING.getStatus(),OrderPlateCutStatusEnum.OPENED.getStatus()); @@ -1312,6 +1322,18 @@ public class PlanServiceImpl implements PlanService { AssertUtils.empty(plateDOList, PLAN_PLATE_IS_CUTTED); + Map sourceBlockIdByPlateId = plateDOS.stream() + .filter(plate -> StringUtils.isNotBlank(plate.getSourceBlockItemId())) + .collect(Collectors.toMap(PlateDO::getId, PlateDO::getSourceBlockItemId)); + Map> sourceBlockIdsByPlanId = deleteOrderPlateIds.stream() + .filter(item -> sourceBlockIdByPlateId.containsKey(item.getPlateId())) + .collect(Collectors.groupingBy( + OrderPlateIds::getPlanId, + Collectors.mapping( + item -> sourceBlockIdByPlateId.get(item.getPlateId()), + Collectors.collectingAndThen(Collectors.toList(), + list -> list.stream().distinct().toList())))); + // 移除排单板件 & 重新计算订单和材质下的数量和面积 removePlanOrderAndGoodsPlate(plateDOS, deleteOrderIds); @@ -1341,6 +1363,8 @@ public class PlanServiceImpl implements PlanService { .set(PlanDO::getOrderNos, orderNos)); } } + syncDeletedProducingPlanBlocks( + deletePlans, sourceBlockIdsByPlanId, new HashSet<>(deleteEmptyPlanIdList)); } @@ -1390,6 +1414,12 @@ public class PlanServiceImpl implements PlanService { // 新增排单板件 & 重新计算订单和材质下的数量和面积 insertPlanOrderAndGoodsPlate(planDO.getId(), insertOrderPlateIds, orderIds); + List sourceBlockIds = getSourceBlockIds( + insertOrderPlateIds.stream().map(OrderPlateIds::getPlateId).toList()); + if (CollUtil.isNotEmpty(sourceBlockIds)) { + appendProducingPlanBlocks(planDO, sourceBlockIds); + } + } /** @@ -1941,6 +1971,117 @@ public class PlanServiceImpl implements PlanService { return resultList; } + /** + * 为包含组件来源板件的本地排单创建组件生产排单。 + */ + private void createProducingPlanOrders(List plans) { + String token = null; + for (PlanDO plan : plans) { + List sourceBlockIds = getSourceBlockIdsByPlanId(plan.getId()); + if (CollUtil.isEmpty(sourceBlockIds)) { + continue; + } + if (plan.getProcessId() == null) { + throw new ServiceException(PRODUCING_PLAN_ORDER_SYNC_FAILED, + "workflowSchemeId is required"); + } + if (token == null) { + token = getProducingToken(); + } + Long producingPlanOrderId = producingPlanOrderClient.create( + token, plan.getProcessId(), sourceBlockIds); + plan.setProducingPlanOrderId(producingPlanOrderId); + planMapper.update(new LambdaUpdateWrapper() + .eq(PlanDO::getId, plan.getId()) + .eq(PlanDO::getOrganId, getUserOrganId()) + .set(PlanDO::getProducingPlanOrderId, producingPlanOrderId)); + } + } + + /** + * 向已有组件生产排单追加板件。 + * + *

{@code producingPlanOrderId} 为空表示接入组件系统之前创建的历史排单, + * 此类排单不调用组件生产系统,也不补建远端排单。

+ */ + private void appendProducingPlanBlocks(PlanDO plan, List sourceBlockIds) { + if (plan.getProducingPlanOrderId() == null) { + return; + } + producingPlanOrderClient.appendBlocks( + getProducingToken(), plan.getProducingPlanOrderId(), sourceBlockIds); + } + + /** + * 同步删除排单板件;本地排单已变空时直接删除对方排单。 + */ + private void syncDeletedProducingPlanBlocks( + List plans, + Map> sourceBlockIdsByPlanId, + Set deletedPlanIds) { + String token = null; + for (PlanDO plan : plans) { + if (plan.getProducingPlanOrderId() == null) { + continue; + } + List sourceBlockIds = sourceBlockIdsByPlanId.get(plan.getId()); + if (!deletedPlanIds.contains(plan.getId()) && CollUtil.isEmpty(sourceBlockIds)) { + continue; + } + if (token == null) { + token = getProducingToken(); + } + if (deletedPlanIds.contains(plan.getId())) { + producingPlanOrderClient.delete(token, plan.getProducingPlanOrderId()); + } else { + producingPlanOrderClient.deleteBlocks( + token, plan.getProducingPlanOrderId(), sourceBlockIds); + } + } + } + + /** + * 删除本地排单时同步删除其组件生产排单。 + */ + private void deleteProducingPlanOrders(List plans) { + String token = null; + for (PlanDO plan : plans) { + if (plan.getProducingPlanOrderId() == null) { + continue; + } + if (token == null) { + token = getProducingToken(); + } + producingPlanOrderClient.delete(token, plan.getProducingPlanOrderId()); + } + } + + private List getSourceBlockIdsByPlanId(Long planId) { + return plateMapper.selectList(new LambdaQueryWrapper() + .eq(PlateDO::getPlanId, planId) + .eq(PlateDO::getOrganId, getUserOrganId())) + .stream() + .map(PlateDO::getSourceBlockItemId) + .filter(StringUtils::isNotBlank) + .distinct() + .toList(); + } + + private List getSourceBlockIds(Collection plateIds) { + if (CollUtil.isEmpty(plateIds)) { + return Collections.emptyList(); + } + return plateMapper.selectByIds(plateIds).stream() + .map(PlateDO::getSourceBlockItemId) + .filter(StringUtils::isNotBlank) + .distinct() + .toList(); + } + + private String getProducingToken() { + return authApi.getMesJwtToken().getCheckedData(); + } + @Override public void getPlanLock(OrderPlanLockReqVO orderPlanLockReqVO) { Long planId = orderPlanLockReqVO.getPlanId(); diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderClient.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderClient.java new file mode 100644 index 000000000..36aac75d3 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderClient.java @@ -0,0 +1,191 @@ +package com.cf.imes.module.executor.service.plan.producing; + +import com.cf.imes.framework.common.exception.ServiceException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static com.cf.imes.module.executor.enums.ErrorCodeConstants.PRODUCING_PLAN_ORDER_SYNC_FAILED; + +/** + * 组件生产系统生产排单客户端。 + */ +@Component +@Slf4j +public class ProducingPlanOrderClient { + + private final ProducingPlanOrderProperties properties; + private final ObjectMapper objectMapper; + private final HttpClient httpClient; + + public ProducingPlanOrderClient(ProducingPlanOrderProperties properties, + ObjectMapper objectMapper) { + this.properties = properties; + this.objectMapper = objectMapper; + this.httpClient = HttpClient.newBuilder() + .connectTimeout(properties.getConnectTimeout()) + .build(); + } + + public Long create(String token, Long workflowSchemeId, List blockIds) { + CreateRequest request = new CreateRequest(blockIds, workflowSchemeId); + HttpResponse response = send( + properties.getBaseUrl(), token, "POST", serialize(request), "create"); + try { + JsonNode result = objectMapper.readTree(response.body()); + JsonNode planOrderIdNode = result; + if (result != null && result.isObject()) { + JsonNode code = result.get("code"); + if (code == null || !code.isIntegralNumber() || code.intValue() != 0) { + String message = result.path("message").asText("business error"); + log.error("Producing plan order create failed, responseBody={}", + responseBodyForLog(response.body())); + throw syncError("create: " + message); + } + planOrderIdNode = result.get("data"); + } + if (planOrderIdNode == null + || (!planOrderIdNode.isIntegralNumber() && !planOrderIdNode.isTextual())) { + throw syncError("create: invalid plan order id"); + } + return planOrderIdNode.isIntegralNumber() + ? planOrderIdNode.longValue() : Long.valueOf(planOrderIdNode.textValue()); + } catch (JsonProcessingException | NumberFormatException ex) { + log.error("Producing plan order create response cannot be parsed, responseBody={}", + responseBodyForLog(response.body()), ex); + throw syncError("create: invalid plan order id"); + } + } + + public void appendBlocks(String token, Long planOrderId, List blockIds) { + HttpResponse response = send( + resourceUrl(planOrderId, "append-block-id"), token, "POST", + serialize(new AppendRequest(blockIds)), "append-block-id"); + validateBusinessResponse(response, "append-block-id"); + } + + public void deleteBlocks(String token, Long planOrderId, List blockIds) { + HttpResponse response = send( + resourceUrl(planOrderId, "delete-block-id"), token, "DELETE", + serialize(new DeleteBlocksRequest(blockIds)), "delete-block-id"); + validateBusinessResponse(response, "delete-block-id"); + } + + public void delete(String token, Long planOrderId) { + HttpResponse response = send( + properties.getBaseUrl() + "/" + planOrderId, token, "DELETE", + null, "delete"); + validateBusinessResponse(response, "delete"); + } + + private String resourceUrl(Long planOrderId, String action) { + return properties.getBaseUrl() + "/" + planOrderId + "/" + action; + } + + private String serialize(Object request) { + try { + return objectMapper.writeValueAsString(request); + } catch (JsonProcessingException ex) { + throw syncError("cannot serialize request"); + } + } + + private HttpResponse send(String url, String token, String method, + String body, String operation) { + HttpRequest.BodyPublisher publisher = body == null + ? HttpRequest.BodyPublishers.noBody() + : HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8); + HttpRequest request = HttpRequest.newBuilder(URI.create(url)) + .timeout(properties.getReadTimeout()) + .header("Authorization", "Bearer " + token) + .header("Accept", "application/json") + .header("Content-Type", "application/json; charset=utf-8") + .method(method, publisher) + .build(); + try { + HttpResponse response = httpClient.send( + request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + log.error("Producing plan order operation {} failed, url={}, httpStatus={}, " + + "responseBody={}", + operation, url, response.statusCode(), + responseBodyForLog(response.body())); + throw syncError(operation + ": HTTP " + response.statusCode()); + } + return response; + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw syncError(operation + ": request interrupted"); + } catch (IOException ex) { + log.warn("Producing plan order operation {} failed", operation, ex); + throw syncError(operation + ": " + ex.getMessage()); + } + } + + /** + * 对方写接口可能以 HTTP 200 返回统一业务响应,此处补充业务码校验。 + * 空响应或未包含 code 的响应仍按接口文档视为成功。 + */ + private void validateBusinessResponse(HttpResponse response, String operation) { + if (response.body() == null || response.body().isBlank()) { + return; + } + try { + JsonNode result = objectMapper.readTree(response.body()); + JsonNode code = result == null || !result.isObject() ? null : result.get("code"); + if (code != null && code.isIntegralNumber() && code.intValue() != 0) { + String message = result.path("message").asText("business error"); + log.error("Producing plan order operation {} failed, responseBody={}", + operation, responseBodyForLog(response.body())); + throw syncError(operation + ": " + message); + } + } catch (JsonProcessingException ex) { + log.warn("Producing plan order operation {} returned a non-JSON success response, " + + "responseBody={}", + operation, responseBodyForLog(response.body())); + } + } + + private String responseBodyForLog(String body) { + if (body == null) { + return ""; + } + int maxLength = 4000; + return body.length() <= maxLength ? body : body.substring(0, maxLength) + "...(truncated)"; + } + + private ServiceException syncError(String message) { + return new ServiceException(PRODUCING_PLAN_ORDER_SYNC_FAILED, message); + } + + @Data + @AllArgsConstructor + private static class CreateRequest { + private List blockIds; + private Long workflowSchemeId; + } + + @Data + @AllArgsConstructor + private static class AppendRequest { + private List appendBlockIds; + } + + @Data + @AllArgsConstructor + private static class DeleteBlocksRequest { + private List deleteBlockIds; + } +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderProperties.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderProperties.java new file mode 100644 index 000000000..4432e6641 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderProperties.java @@ -0,0 +1,23 @@ +package com.cf.imes.module.executor.service.plan.producing; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.time.Duration; + +/** + * 组件生产系统生产排单接口配置。 + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "chenfeng.executor.producing-plan-order") +public class ProducingPlanOrderProperties { + + private String baseUrl = + "http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/plan-order"; + + private Duration connectTimeout = Duration.ofSeconds(5); + + private Duration readTimeout = Duration.ofSeconds(10); +} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties index cc7cdcec1..a429c14e5 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_en.properties @@ -152,3 +152,4 @@ parts.heapsplit.num.not.enough=The number of accessories that can be stacked is parts.heapsplit.item.source.not.exist=Abnormal accessory stacking data, please refresh and stack again plan.not.allow.delete=Plan number: {0} has been cut and cannot be deleted +producing.plan.order.sync.failed=Failed to synchronize component production plan order: {0} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties index 872e3ceed..480a7ff20 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/resources/message_errorcode_zh.properties @@ -152,3 +152,4 @@ parts.heapsplit.num.not.enough=配件可分堆数量不足,请刷新后再次 parts.heapsplit.item.source.not.exist=配件分堆数据异常,请刷新后再次分堆 plan.not.allow.delete=排单号:{0}已开料无法删除 +producing.plan.order.sync.failed=同步组件生产排单失败:{0} diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderClientTest.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderClientTest.java new file mode 100644 index 000000000..0a2d564b6 --- /dev/null +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/test/java/com/cf/imes/module/executor/service/plan/producing/ProducingPlanOrderClientTest.java @@ -0,0 +1,93 @@ +package com.cf.imes.module.executor.service.plan.producing; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class ProducingPlanOrderClientTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final List requests = new ArrayList<>(); + private HttpServer server; + private ProducingPlanOrderClient client; + + @BeforeEach + void setUp() throws IOException { + server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/api/v1/producing/plan-order", this::handle); + server.start(); + + ProducingPlanOrderProperties properties = new ProducingPlanOrderProperties(); + properties.setBaseUrl("http://localhost:" + server.getAddress().getPort() + + "/api/v1/producing/plan-order"); + client = new ProducingPlanOrderClient(properties, objectMapper); + } + + @AfterEach + void tearDown() { + server.stop(0); + } + + @Test + void shouldUseSourceBlockIdsForAllFourOperations() throws Exception { + Long remotePlanId = client.create("mes-token", 36L, List.of("source-1", "source-2")); + client.appendBlocks("mes-token", remotePlanId, List.of("source-3")); + client.deleteBlocks("mes-token", remotePlanId, List.of("source-2")); + client.delete("mes-token", remotePlanId); + + assertThat(remotePlanId).isEqualTo(98765L); + assertThat(requests).hasSize(4); + assertThat(requests).extracting(RecordedRequest::method) + .containsExactly("POST", "POST", "DELETE", "DELETE"); + assertThat(requests).extracting(RecordedRequest::path) + .containsExactly( + "/api/v1/producing/plan-order", + "/api/v1/producing/plan-order/98765/append-block-id", + "/api/v1/producing/plan-order/98765/delete-block-id", + "/api/v1/producing/plan-order/98765"); + assertThat(requests).extracting(RecordedRequest::authorization) + .containsOnly("Bearer mes-token"); + + JsonNode createBody = objectMapper.readTree(requests.get(0).body()); + assertThat(createBody.get("workflowSchemeId").longValue()).isEqualTo(36L); + assertThat(createBody.get("blockIds").toString()) + .isEqualTo("[\"source-1\",\"source-2\"]"); + assertThat(objectMapper.readTree(requests.get(1).body()) + .get("appendBlockIds").get(0).textValue()).isEqualTo("source-3"); + assertThat(objectMapper.readTree(requests.get(2).body()) + .get("deleteBlockIds").get(0).textValue()).isEqualTo("source-2"); + } + + private void handle(HttpExchange exchange) throws IOException { + String body = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + requests.add(new RecordedRequest( + exchange.getRequestMethod(), + exchange.getRequestURI().getPath(), + exchange.getRequestHeaders().getFirst("Authorization"), + body)); + byte[] response = exchange.getRequestURI().getPath() + .equals("/api/v1/producing/plan-order") + ? """ + {"data":98765,"code":0,"message":"Request processed successfully."} + """.getBytes(StandardCharsets.UTF_8) : new byte[0]; + exchange.sendResponseHeaders(200, response.length); + exchange.getResponseBody().write(response); + exchange.close(); + } + + private record RecordedRequest( + String method, String path, String authorization, String body) { + } +} diff --git a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java index 83eaf926e..920895a9a 100644 --- a/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java +++ b/cf-module-prod-plan/cf-module-prod-plan-biz/src/main/java/com/cf/imes/module/plan/service/orderImport/factory/ProducingOrderImportFactory.java @@ -104,6 +104,16 @@ public class ProducingOrderImportFactory { */ private static final int BATCH_THRESHOLD_NUMBER = 999; + /** + * 板件批量写入阈值。板件字段较多,单独设置以便与其他表的批量写入性能进行对比。 + */ + private static final int ORDER_PLATE_BATCH_THRESHOLD_NUMBER = 2000; + + /** + * 订单元素及关系批量写入阈值。 + */ + private static final int ORDER_ITEM_BATCH_THRESHOLD_NUMBER = 2000; + private SnowFlakeGenerator snowFlakeGenerator; private OrderMapper orderMapper; @@ -173,8 +183,8 @@ public class ProducingOrderImportFactory { // 对方 partsItems.id:iMES 基础 order_item.id private Map partSerialNoMap = new LinkedHashMap<>(); - private List plateDOS = new ArrayList<>(); - private List orderItemDOS = new ArrayList<>(); + private List plateDOS = ListUtils.newArrayListWithExpectedSize(ORDER_PLATE_BATCH_THRESHOLD_NUMBER); + private List orderItemDOS = ListUtils.newArrayListWithExpectedSize(ORDER_ITEM_BATCH_THRESHOLD_NUMBER); private List orderPartsDOS = new ArrayList<>(); private List goodsDOS = new ArrayList<>(); private List orderComponentDOS = new ArrayList<>(); @@ -2388,7 +2398,7 @@ public class ProducingOrderImportFactory { */ private void orderPlateBatchInsertWithinThreshold(List list, boolean isLast) { // 达到批量的阈值就做一次插入 - if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) { + if (CollUtil.isNotEmpty(list) && (list.size() >= ORDER_PLATE_BATCH_THRESHOLD_NUMBER || isLast)) { int batchRows = list.size(); long batchStartedAt = System.nanoTime(); long[] compressionNanos = {0L}; @@ -2495,7 +2505,7 @@ public class ProducingOrderImportFactory { * 重建板件批量写入缓冲区。 */ private void clearPlate() { - plateDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER); + plateDOS = ListUtils.newArrayListWithExpectedSize(ORDER_PLATE_BATCH_THRESHOLD_NUMBER); } /** @@ -2506,7 +2516,7 @@ public class ProducingOrderImportFactory { */ private void orderItemBatchInsertWithinThreshold(List list, boolean isLast) { // 达到批量的阈值就做一次插入 - if (CollUtil.isNotEmpty(list) && (list.size() >= BATCH_THRESHOLD_NUMBER || isLast)) { + if (CollUtil.isNotEmpty(list) && (list.size() >= ORDER_ITEM_BATCH_THRESHOLD_NUMBER || isLast)) { int batchRows = list.size(); long batchStartedAt = System.nanoTime(); @@ -2573,7 +2583,7 @@ public class ProducingOrderImportFactory { * 重建订单元素批量写入缓冲区。 */ private void clearItem() { - orderItemDOS = ListUtils.newArrayListWithExpectedSize(BATCH_THRESHOLD_NUMBER); + orderItemDOS = ListUtils.newArrayListWithExpectedSize(ORDER_ITEM_BATCH_THRESHOLD_NUMBER); } /**