| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.system.service.agent.imports; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService; |
| | | import cn.iocoder.yudao.module.system.api.agent.imports.*; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.agent.AgentImportCommandDO; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.agent.AgentImportCommandRowDO; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.agent.AgentImportCommandMapper; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.agent.AgentImportCommandRowMapper; |
| | | import cn.iocoder.yudao.module.system.framework.agent.AgentBridgeException; |
| | | import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal; |
| | | import cn.iocoder.yudao.module.system.service.storage.SystemAgentImportBridgeService; |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.beans.factory.ObjectProvider; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.dao.DuplicateKeyException; |
| | | import org.springframework.http.HttpStatus; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.PlatformTransactionManager; |
| | | import org.springframework.transaction.support.TransactionTemplate; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.MessageDigest; |
| | | import java.security.NoSuchAlgorithmException; |
| | | import java.time.Duration; |
| | | import java.time.LocalDateTime; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * éç¨ä¸å¡å¯¼å
¥ç¼æï¼éé
å¨åªè´è´£åè¡æ ¡éª/åå
¥ï¼æ¬ç±»ç»ä¸å¤çæéãå¹çãéè¡äºå¡ä¸åæ§ã |
| | | */ |
| | | @Service |
| | | @Validated |
| | | public class SystemAgentGenericImportService { |
| | | |
| | | public static final String STATUS_RUNNING = "RUNNING"; |
| | | public static final String STATUS_SUCCEEDED = "SUCCEEDED"; |
| | | public static final String STATUS_PARTIAL_FAILED = "PARTIAL_FAILED"; |
| | | public static final String STATUS_FAILED = "FAILED"; |
| | | public static final String STATUS_UNKNOWN = "UNKNOWN"; |
| | | |
| | | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
| | | private static final Duration LEASE_DURATION = Duration.ofMinutes(5); |
| | | |
| | | @Resource |
| | | private ObjectProvider<AgentImportModuleAdapter> adapterProvider; |
| | | @Resource |
| | | private SecurityFrameworkService securityFrameworkService; |
| | | @Resource |
| | | private SystemAgentImportBridgeService attachmentService; |
| | | @Resource |
| | | private AgentImportCommandMapper commandMapper; |
| | | @Resource |
| | | private AgentImportCommandRowMapper commandRowMapper; |
| | | @Resource |
| | | private PlatformTransactionManager transactionManager; |
| | | |
| | | @Value("${yudao.agent.bridge.write-enabled:false}") |
| | | private boolean writeEnabled; |
| | | |
| | | @Value("${yudao.agent.bridge.max-rows:5000}") |
| | | private int maxRows; |
| | | |
| | | public List<Map<String, String>> modules() { |
| | | return adapters().values().stream() |
| | | .map(adapter -> Map.of("moduleId", adapter.moduleId(), "permission", adapter.permission())) |
| | | .toList(); |
| | | } |
| | | |
| | | public AgentImportPreflightRespVO preflight(String moduleId, AgentImportPreflightReqVO request, |
| | | AgentPrincipal principal) { |
| | | AgentImportModuleAdapter adapter = requireAdapter(moduleId); |
| | | requirePermission(adapter); |
| | | validateAttachment(request.getAttachmentId(), request.getSourceSha256()); |
| | | validateRows(request); |
| | | AgentImportPreflightRespVO result = adapter.preflight(principal, request); |
| | | validatePlan(result, request); |
| | | return result; |
| | | } |
| | | |
| | | public AgentImportReceiptRespVO commit(String moduleId, AgentImportCommitReqVO request, |
| | | AgentPrincipal principal) { |
| | | if (!writeEnabled) { |
| | | throw new AgentBridgeException(HttpStatus.SERVICE_UNAVAILABLE, "WRITE_DISABLED", |
| | | "æºè½ä½åå
¥å¼å
³æªæå¼ï¼æäº¤å·²æç»"); |
| | | } |
| | | AgentImportModuleAdapter adapter = requireAdapter(moduleId); |
| | | requirePermission(adapter); |
| | | validateAttachment(request.getAttachmentId(), request.getSourceSha256()); |
| | | validateRows(request); |
| | | |
| | | Long ownerUserId = principal.userId(); |
| | | String digest = requestDigest(moduleId, request); |
| | | |
| | | // 1. å¹çå½ä¸ï¼å½ä»¤ ID ä¼å
ï¼å
¶æ¬¡å¹çé® |
| | | AgentImportCommandDO existing = commandMapper.selectByCommandId( |
| | | ownerUserId, moduleId, request.getCommandId()); |
| | | if (existing != null) { |
| | | requireSameDigest(existing, digest); |
| | | return toReceipt(existing); |
| | | } |
| | | existing = commandMapper.selectByIdempotencyKey( |
| | | ownerUserId, moduleId, request.getIdempotencyKey()); |
| | | if (existing != null) { |
| | | requireSameDigest(existing, digest); |
| | | return toReceipt(existing); |
| | | } |
| | | |
| | | // 2. 鿰颿£å¹¶æ¯å¯¹è®¡åæè¦ï¼é²æ¢ç¡®è®¤åä¸»æ°æ®åå |
| | | AgentImportPreflightRespVO plan = adapter.preflight(principal, request); |
| | | validatePlan(plan, request); |
| | | if (!StrUtil.equalsIgnoreCase(request.getPreflightHash(), plan.getPreflightHash())) { |
| | | throw new AgentBridgeException(HttpStatus.CONFLICT, "PLAN_STALE", |
| | | "颿£è®¡åå·²ååï¼è¯·éæ°é¢æ£å¹¶ç¡®è®¤", false, Map.of("preflight", plan)); |
| | | } |
| | | if (!Boolean.TRUE.equals(plan.getCanCommit())) { |
| | | throw new AgentBridgeException(HttpStatus.UNPROCESSABLE_ENTITY, "PREFLIGHT_BLOCKED", |
| | | "颿£åå¨é»æé¡¹ï¼ä¸è½æäº¤", false, Map.of("preflight", plan)); |
| | | } |
| | | |
| | | // 3. ç»è®°å½ä»¤ï¼å¯ä¸é®å
ä½å¹¶åéå¤æäº¤ï¼ |
| | | AgentImportCommandDO command = AgentImportCommandDO.builder() |
| | | .commandId(request.getCommandId()) |
| | | .idempotencyKey(request.getIdempotencyKey()) |
| | | .requestDigest(digest) |
| | | .moduleId(moduleId) |
| | | .ownerUserId(ownerUserId) |
| | | .sessionId(request.getSessionId()) |
| | | .attachmentId(request.getAttachmentId()) |
| | | .sourceSha256(request.getSourceSha256().toLowerCase(Locale.ROOT)) |
| | | .preflightHash(request.getPreflightHash().toLowerCase(Locale.ROOT)) |
| | | .proposalHash(request.getProposalHash()) |
| | | .status(STATUS_RUNNING) |
| | | .totalCount(request.getRows().size()) |
| | | .successCount(0).failedCount(0).skippedCount(0) |
| | | .leaseExpiresAt(LocalDateTime.now().plus(LEASE_DURATION)) |
| | | .build(); |
| | | try { |
| | | commandMapper.insert(command); |
| | | } catch (DuplicateKeyException ex) { |
| | | AgentImportCommandDO raced = commandMapper.selectByCommandId( |
| | | ownerUserId, moduleId, request.getCommandId()); |
| | | if (raced == null) { |
| | | raced = commandMapper.selectByIdempotencyKey( |
| | | ownerUserId, moduleId, request.getIdempotencyKey()); |
| | | } |
| | | if (raced == null) { |
| | | throw ex; |
| | | } |
| | | requireSameDigest(raced, digest); |
| | | return toReceipt(raced); |
| | | } |
| | | |
| | | // 4. éè¡ç¬ç«äºå¡æ§è¡ï¼åè¡å¤±è´¥ä¸å½±åå
¶ä»è¡ |
| | | int success = 0; |
| | | int failed = 0; |
| | | int skipped = 0; |
| | | TransactionTemplate rowTx = new TransactionTemplate(transactionManager); |
| | | try { |
| | | for (int index = 0; index < request.getRows().size(); index++) { |
| | | AgentImportPreflightReqVO.Row row = request.getRows().get(index); |
| | | AgentImportPreflightRespVO.Row planned = plan.getRows().get(index); |
| | | if ("SKIP".equals(planned.getAction())) { |
| | | commandRowMapper.insert(baseRow(moduleId, request.getCommandId(), row) |
| | | .action("SKIP").status("SKIPPED") |
| | | .targetItemId(planned.getTargetId()).retryable(false).build()); |
| | | skipped++; |
| | | continue; |
| | | } |
| | | if (!"CREATE".equals(planned.getAction())) { |
| | | commandRowMapper.insert(baseRow(moduleId, request.getCommandId(), row) |
| | | .action("CREATE").status("FAILED") |
| | | .errorCode("PREFLIGHT_BLOCKED").errorMessage("该è¡é¢æ£æªéè¿").retryable(false).build()); |
| | | failed++; |
| | | continue; |
| | | } |
| | | try { |
| | | AgentImportRowReceipt receipt = rowTx.execute(status -> |
| | | adapter.commitRow(principal, request, planned, row)); |
| | | if (receipt == null) { |
| | | throw new IllegalStateException("éé
卿ªè¿ååæ§"); |
| | | } |
| | | commandRowMapper.insert(baseRow(moduleId, request.getCommandId(), row) |
| | | .action(StrUtil.blankToDefault(receipt.action(), "CREATE")) |
| | | .status("SUCCESS") |
| | | .targetItemId(receipt.targetId()) |
| | | .targetVersion(receipt.targetVersion()) |
| | | .code(StrUtil.blankToDefault(receipt.code(), row.getValues().get("code"))) |
| | | .name(StrUtil.blankToDefault(receipt.name(), row.getValues().get("name"))) |
| | | .retryable(false).build()); |
| | | success++; |
| | | } catch (Exception ex) { |
| | | recordFailure(moduleId, request.getCommandId(), row, ex); |
| | | failed++; |
| | | } |
| | | } |
| | | } catch (Exception ex) { |
| | | command.setStatus(STATUS_UNKNOWN); |
| | | command.setErrorCode("COMMIT_INTERRUPTED"); |
| | | command.setErrorMessage(trim(ex.getMessage(), 512)); |
| | | command.setSuccessCount(success); |
| | | command.setFailedCount(failed); |
| | | command.setSkippedCount(skipped); |
| | | command.setUpdateTime(LocalDateTime.now()); |
| | | commandMapper.updateById(command); |
| | | return toReceipt(command); |
| | | } |
| | | |
| | | command.setSuccessCount(success); |
| | | command.setFailedCount(failed); |
| | | command.setSkippedCount(skipped); |
| | | command.setStatus(failed == 0 ? STATUS_SUCCEEDED |
| | | : (success + skipped == 0 ? STATUS_FAILED : STATUS_PARTIAL_FAILED)); |
| | | command.setLeaseExpiresAt(LocalDateTime.now()); |
| | | command.setUpdateTime(LocalDateTime.now()); |
| | | commandMapper.updateById(command); |
| | | return toReceipt(command); |
| | | } |
| | | |
| | | public AgentImportReceiptRespVO getCommand(String moduleId, String commandId, AgentPrincipal principal) { |
| | | requireAdapter(moduleId); |
| | | AgentImportCommandDO command = commandMapper.selectByCommandId( |
| | | principal.userId(), moduleId, commandId); |
| | | if (command == null) { |
| | | throw new AgentBridgeException(HttpStatus.NOT_FOUND, "COMMAND_NOT_FOUND", "导å
¥å½ä»¤ä¸åå¨"); |
| | | } |
| | | return toReceipt(command); |
| | | } |
| | | |
| | | private void recordFailure(String moduleId, String commandId, AgentImportPreflightReqVO.Row row, |
| | | Exception ex) { |
| | | String errorCode = "ROW_WRITE_FAILED"; |
| | | boolean retryable = true; |
| | | String message = ex.getMessage(); |
| | | if (ex instanceof AgentBridgeException bridge) { |
| | | errorCode = bridge.getErrorCode(); |
| | | retryable = bridge.isRetryable(); |
| | | message = bridge.getReason(); |
| | | } else if (ex instanceof ServiceException service) { |
| | | errorCode = "ROW_WRITE_REJECTED"; |
| | | retryable = false; |
| | | message = service.getMessage(); |
| | | } |
| | | commandRowMapper.insert(baseRow(moduleId, commandId, row) |
| | | .action("CREATE").status("FAILED") |
| | | .errorCode(errorCode).errorMessage(trim(message, 512)).retryable(retryable).build()); |
| | | } |
| | | |
| | | private AgentImportPreflightRespVO.Row requirePlanRow(AgentImportPreflightRespVO plan, int index) { |
| | | if (plan.getRows() == null || index >= plan.getRows().size()) { |
| | | throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "ADAPTER_CONTRACT_INVALID", |
| | | "éé
å¨è¿åç颿£è¡æ°ä¸è¯·æ±ä¸ä¸è´"); |
| | | } |
| | | return plan.getRows().get(index); |
| | | } |
| | | |
| | | private void validatePlan(AgentImportPreflightRespVO plan, AgentImportPreflightReqVO request) { |
| | | if (plan == null || StrUtil.isBlank(plan.getPreflightHash())) { |
| | | throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "ADAPTER_CONTRACT_INVALID", |
| | | "éé
卿ªè¿å颿£æè¦"); |
| | | } |
| | | if (plan.getRows() == null || plan.getRows().size() != request.getRows().size()) { |
| | | throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "ADAPTER_CONTRACT_INVALID", |
| | | "éé
å¨è¿åç颿£è¡æ°ä¸è¯·æ±ä¸ä¸è´"); |
| | | } |
| | | for (int index = 0; index < plan.getRows().size(); index++) { |
| | | if (!request.getRows().get(index).getCandidateId() |
| | | .equals(plan.getRows().get(index).getCandidateId())) { |
| | | throw new AgentBridgeException(HttpStatus.BAD_GATEWAY, "ADAPTER_CONTRACT_INVALID", |
| | | "éé
å¨è¿åç颿£è¡é¡ºåºä¸è¯·æ±ä¸ä¸è´"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void validateAttachment(Long attachmentId, String sourceSha256) { |
| | | String actual = attachmentService.sha256OfAttachment(attachmentId); |
| | | if (!StrUtil.equalsIgnoreCase(actual, sourceSha256)) { |
| | | throw new AgentBridgeException(HttpStatus.CONFLICT, "SOURCE_CHANGED", |
| | | "éä»¶å
容已ååï¼è¯·éæ°ä¸ä¼ ååè¯", true, null); |
| | | } |
| | | } |
| | | |
| | | private void validateRows(AgentImportPreflightReqVO request) { |
| | | if (request.getRows() == null || request.getRows().isEmpty()) { |
| | | throw new AgentBridgeException(HttpStatus.UNPROCESSABLE_ENTITY, "ROWS_EMPTY", "没æå¯å¯¼å
¥çæ°æ®è¡"); |
| | | } |
| | | if (request.getRows().size() > maxRows) { |
| | | throw new AgentBridgeException(HttpStatus.UNPROCESSABLE_ENTITY, "ROWS_TOO_MANY", |
| | | "忬¡å¯¼å
¥è¶
è¿ " + maxRows + " è¡ï¼è¯·æåæä»¶"); |
| | | } |
| | | } |
| | | |
| | | private void requirePermission(AgentImportModuleAdapter adapter) { |
| | | for (String permission : adapter.permissions()) { |
| | | if (!securityFrameworkService.hasPermission(permission)) { |
| | | throw new AgentBridgeException(HttpStatus.FORBIDDEN, "FORBIDDEN", |
| | | "å½åç¨æ·ç¼ºå°è¯¥æ¨¡åç导å
¥æéï¼" + permission + "ï¼"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private AgentImportModuleAdapter requireAdapter(String moduleId) { |
| | | AgentImportModuleAdapter adapter = adapters().get(moduleId); |
| | | if (adapter == null) { |
| | | throw new AgentBridgeException(HttpStatus.NOT_FOUND, "MODULE_NOT_SUPPORTED", |
| | | "䏿¯æç导å
¥æ¨¡åï¼" + moduleId); |
| | | } |
| | | return adapter; |
| | | } |
| | | |
| | | private Map<String, AgentImportModuleAdapter> adapters() { |
| | | Map<String, AgentImportModuleAdapter> result = new LinkedHashMap<>(); |
| | | for (AgentImportModuleAdapter adapter : adapterProvider) { |
| | | result.put(adapter.moduleId(), adapter); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private static AgentImportCommandRowDO.AgentImportCommandRowDOBuilder baseRow( |
| | | String moduleId, String commandId, AgentImportPreflightReqVO.Row row) { |
| | | return AgentImportCommandRowDO.builder() |
| | | .commandId(commandId) |
| | | .moduleId(moduleId) |
| | | .candidateId(row.getCandidateId()) |
| | | .sourceRow(row.getRowNo()) |
| | | .code(row.getValues().get("code")) |
| | | .name(row.getValues().get("name")); |
| | | } |
| | | |
| | | private static void requireSameDigest(AgentImportCommandDO command, String digest) { |
| | | if (!command.getRequestDigest().equals(digest)) { |
| | | throw new AgentBridgeException(HttpStatus.CONFLICT, "IDEMPOTENCY_CONFLICT", |
| | | "åä¸å½ä»¤æå¹ç鮿ºå¸¦äºä¸åç请æ±å
容ï¼è¯·æ£æ¥ä»»å¡ç¶æåéè¯"); |
| | | } |
| | | } |
| | | |
| | | private static String requestDigest(String moduleId, AgentImportCommitReqVO request) { |
| | | Map<String, Object> canonical = new LinkedHashMap<>(); |
| | | canonical.put("module_id", moduleId); |
| | | canonical.put("attachment_id", request.getAttachmentId()); |
| | | canonical.put("session_id", request.getSessionId()); |
| | | canonical.put("source_sha256", StrUtil.nullToEmpty(request.getSourceSha256()).toLowerCase(Locale.ROOT)); |
| | | canonical.put("preflight_hash", StrUtil.nullToEmpty(request.getPreflightHash()).toLowerCase(Locale.ROOT)); |
| | | canonical.put("proposal_hash", StrUtil.nullToEmpty(request.getProposalHash())); |
| | | canonical.put("proposal_revision", StrUtil.nullToEmpty(request.getProposalRevision())); |
| | | canonical.put("options", request.getOptions() == null ? Map.of() : new TreeMap<>(request.getOptions())); |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | for (AgentImportPreflightReqVO.Row row : request.getRows()) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("candidate_id", row.getCandidateId()); |
| | | item.put("row_no", row.getRowNo()); |
| | | item.put("values", row.getValues() == null ? Map.of() : new TreeMap<>(row.getValues())); |
| | | rows.add(item); |
| | | } |
| | | canonical.put("rows", rows); |
| | | try { |
| | | byte[] payload = OBJECT_MAPPER.writeValueAsBytes(canonical); |
| | | return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(payload)); |
| | | } catch (NoSuchAlgorithmException ex) { |
| | | throw new IllegalStateException("SHA-256 ä¸å¯ç¨", ex); |
| | | } catch (JsonProcessingException ex) { |
| | | throw new IllegalStateException("è¯·æ±æè¦çæå¤±è´¥", ex); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * éé
å¨è®¡ç®é¢æ£æè¦çç»ä¸å
¥å£ï¼é¿å
忍¡åæè¦å£å¾æ¼ç§»ã |
| | | */ |
| | | public static String preflightHash(String moduleId, AgentImportPreflightReqVO request, |
| | | Object resolvedPayload) { |
| | | Map<String, Object> canonical = new LinkedHashMap<>(); |
| | | canonical.put("module_id", moduleId); |
| | | canonical.put("attachment_id", request.getAttachmentId()); |
| | | canonical.put("source_sha256", StrUtil.nullToEmpty(request.getSourceSha256()).toLowerCase(Locale.ROOT)); |
| | | canonical.put("proposal_revision", StrUtil.nullToEmpty(request.getProposalRevision())); |
| | | canonical.put("options", request.getOptions() == null ? Map.of() : new TreeMap<>(request.getOptions())); |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | for (AgentImportPreflightReqVO.Row row : request.getRows()) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("candidate_id", row.getCandidateId()); |
| | | item.put("row_no", row.getRowNo()); |
| | | item.put("values", row.getValues() == null ? Map.of() : new TreeMap<>(row.getValues())); |
| | | rows.add(item); |
| | | } |
| | | canonical.put("rows", rows); |
| | | canonical.put("resolved", resolvedPayload); |
| | | try { |
| | | byte[] payload = OBJECT_MAPPER.writeValueAsBytes(canonical); |
| | | return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(payload)); |
| | | } catch (NoSuchAlgorithmException ex) { |
| | | throw new IllegalStateException("SHA-256 ä¸å¯ç¨", ex); |
| | | } catch (JsonProcessingException ex) { |
| | | throw new IllegalStateException("颿£æè¦çæå¤±è´¥", ex); |
| | | } |
| | | } |
| | | |
| | | private AgentImportReceiptRespVO toReceipt(AgentImportCommandDO command) { |
| | | String status = command.getStatus(); |
| | | if (STATUS_RUNNING.equals(status) && command.getLeaseExpiresAt() != null |
| | | && command.getLeaseExpiresAt().isBefore(LocalDateTime.now())) { |
| | | status = STATUS_UNKNOWN; |
| | | } |
| | | List<AgentImportCommandRowDO> rowDOs = commandRowMapper.selectListByCommandId(command.getCommandId()); |
| | | List<AgentImportReceiptRespVO.Row> rows = rowDOs.stream() |
| | | .map(row -> AgentImportReceiptRespVO.Row.builder() |
| | | .candidateId(row.getCandidateId()) |
| | | .rowNo(row.getSourceRow()) |
| | | .code(row.getCode()) |
| | | .name(row.getName()) |
| | | .action(row.getAction()) |
| | | .status(row.getStatus()) |
| | | .targetItemId(row.getTargetItemId()) |
| | | .targetVersion(row.getTargetVersion()) |
| | | .errorCode(row.getErrorCode()) |
| | | .errorMessage(row.getErrorMessage()) |
| | | .retryable(row.getRetryable()) |
| | | .build()) |
| | | .toList(); |
| | | int success = (int) rowDOs.stream().filter(row -> "SUCCESS".equals(row.getStatus())).count(); |
| | | int failed = (int) rowDOs.stream().filter(row -> "FAILED".equals(row.getStatus())).count(); |
| | | int skipped = (int) rowDOs.stream().filter(row -> "SKIPPED".equals(row.getStatus())).count(); |
| | | int unknown = (int) rowDOs.stream().filter(row -> "UNKNOWN".equals(row.getStatus())).count(); |
| | | return AgentImportReceiptRespVO.builder() |
| | | .commandId(command.getCommandId()) |
| | | .idempotencyKey(command.getIdempotencyKey()) |
| | | .status(status) |
| | | .attachmentId(command.getAttachmentId()) |
| | | .sessionId(command.getSessionId()) |
| | | .sourceSha256(command.getSourceSha256()) |
| | | .preflightHash(command.getPreflightHash()) |
| | | .proposalHash(command.getProposalHash()) |
| | | .summary(AgentImportReceiptRespVO.Summary.builder() |
| | | .total(command.getTotalCount()) |
| | | .success(success).failed(failed).skipped(skipped).unknown(unknown) |
| | | .build()) |
| | | .errorCode(command.getErrorCode()) |
| | | .errorMessage(command.getErrorMessage()) |
| | | .rows(rows) |
| | | .createTime(command.getCreateTime()) |
| | | .completedTime(STATUS_RUNNING.equals(command.getStatus()) ? null : command.getUpdateTime()) |
| | | .build(); |
| | | } |
| | | |
| | | private static String trim(String value, int maxLength) { |
| | | if (value == null) { |
| | | return null; |
| | | } |
| | | return value.length() <= maxLength ? value : value.substring(0, maxLength); |
| | | } |
| | | } |