From 43aab58c9ecb1203770ba382efbdbae299ca87ec Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 26 十月 2026 15:12:31 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_business' into dev_business
---
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java | 259 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 259 insertions(+), 0 deletions(-)
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java
new file mode 100644
index 0000000..238192d
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/agent/MdmAgentImportPreflightService.java
@@ -0,0 +1,259 @@
+package cn.iocoder.yudao.module.mdm.service.agent;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.mdm.controller.admin.agent.vo.MdmAgentImportPreflightReqVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.agent.vo.MdmAgentImportPreflightRespVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportExcelVO;
+import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
+import cn.iocoder.yudao.module.mdm.service.item.MdmItemImportContext;
+import cn.iocoder.yudao.module.mdm.service.item.MdmItemRowResolution;
+import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
+import cn.iocoder.yudao.module.system.framework.agent.AgentBridgeException;
+import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal;
+import cn.iocoder.yudao.module.system.framework.agent.SystemAgentBridgeGuard;
+import cn.iocoder.yudao.module.system.service.storage.SystemAgentAttachmentRef;
+import cn.iocoder.yudao.module.system.service.storage.SystemAgentImportBridgeService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.annotation.Resource;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+/**
+ * 鐗╂枡瀵煎叆鍙棰勬锛氭牎楠岄檮浠躲�佽В鏋愬垎绫�/鍗曚綅銆佸垽鏂紪鐮佹槸鍚﹀凡瀛樺湪锛屼笉鍐欎换浣曚笟鍔¤〃銆�
+ */
+@Service
+@Validated
+public class MdmAgentImportPreflightService {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ @Resource
+ private SystemAgentImportBridgeService bridgeService;
+ @Resource
+ private MdmItemService itemService;
+ @Resource
+ private MdmAgentImportPolicy policy;
+
+ public MdmAgentImportPreflightRespVO preflight(MdmAgentImportPreflightReqVO reqVO, AgentPrincipal principal) {
+ if (reqVO.getRows().size() > policy.getMaxRows()) {
+ throw new AgentBridgeException(HttpStatus.PAYLOAD_TOO_LARGE, "ROW_LIMIT_EXCEEDED",
+ "瀵煎叆琛屾暟瓒呰繃涓婇檺锛�" + policy.getMaxRows());
+ }
+ // 1. 闄勪欢涓庢憳瑕佸繀椤讳笌鏈浼氳瘽缁戝畾鐨勪竴鑷�
+ SystemAgentAttachmentRef attachment = bridgeService.requireOwnedAttachmentRef(reqVO.getAttachmentId());
+ if (reqVO.getSessionId() != null && !reqVO.getSessionId().equals(attachment.sessionId())) {
+ throw new AgentBridgeException(HttpStatus.CONFLICT, "SESSION_MISMATCH",
+ "闄勪欢涓嶅睘浜庢寚瀹氳ˉ褰曚細璇濓紝璇烽噸鏂颁笂浼犲悗鍐嶈瘯", true, null);
+ }
+ String sourceSha256 = bridgeService.sha256OfAttachment(reqVO.getAttachmentId());
+ if (!sourceSha256.equalsIgnoreCase(reqVO.getSourceSha256().trim())) {
+ throw new AgentBridgeException(HttpStatus.CONFLICT, "SOURCE_DIGEST_MISMATCH",
+ "闄勪欢鍐呭宸插彉鍖栵紙鎽樿涓嶄竴鑷达級锛岃閲嶆柊涓婁紶鍚庡啀璇�", true, null);
+ }
+ // 2. candidate_id 蹇呴』鍞竴锛屽惁鍒欏洖鎵ф棤娉曢�愯瀵瑰簲
+ Set<String> candidateIds = new HashSet<>();
+ for (MdmAgentImportPreflightReqVO.Row row : reqVO.getRows()) {
+ if (!candidateIds.add(row.getCandidateId())) {
+ throw new AgentBridgeException(HttpStatus.BAD_REQUEST, "CANDIDATE_ID_DUPLICATED",
+ "瀵煎叆琛屾爣璇嗛噸澶嶏細" + row.getCandidateId());
+ }
+ }
+
+ // 3. 棰勭儹鍒嗙被/鍗曚綅鏄犲皠
+ List<MdmItemImportExcelVO> excelRows = reqVO.getRows().stream()
+ .map(MdmAgentImportPreflightService::toExcelVO).toList();
+ MdmItemImportContext context = itemService.buildImportContext(excelRows);
+
+ boolean syncMesRequested = Boolean.TRUE.equals(reqVO.getSyncMes());
+ List<MdmAgentImportPreflightRespVO.Issue> blockingItems = new ArrayList<>();
+ if (syncMesRequested && !policy.isAllowMesSync()) {
+ blockingItems.add(issue("MES_SYNC_DISABLED", "syncMes",
+ "鏈嶅姟鏈紑鏀惧悓姝� MES锛岃鍏抽棴 syncMes 鍚庨噸璇�", false));
+ }
+
+ // 4. 鏂囦欢鍐呴噸澶嶇紪鐮侊細鎵�鏈夐噸澶嶈涓�璧烽樆鏂紝閬垮厤鈥滈琛屽缓銆佹琛岃烦鈥濈殑璇
+ Map<String, Long> codeCounts = reqVO.getRows().stream()
+ .map(MdmAgentImportPreflightReqVO.Row::getCode).filter(StrUtil::isNotBlank)
+ .map(String::trim).collect(java.util.stream.Collectors.groupingBy(code -> code,
+ java.util.stream.Collectors.counting()));
+ Set<String> duplicateCodes = codeCounts.entrySet().stream()
+ .filter(entry -> entry.getValue() > 1).map(Map.Entry::getKey)
+ .collect(java.util.stream.Collectors.toSet());
+
+ // 5. 閫愯鍒ゅ畾 CREATE / SKIP / BLOCKED
+ List<MdmAgentImportPreflightRespVO.Row> rows = new ArrayList<>();
+ int createCount = 0;
+ int skipCount = 0;
+ int blockedCount = 0;
+ for (int index = 0; index < reqVO.getRows().size(); index++) {
+ MdmAgentImportPreflightReqVO.Row row = reqVO.getRows().get(index);
+ MdmItemImportExcelVO excelVO = excelRows.get(index);
+ List<MdmAgentImportPreflightRespVO.Issue> blockers = new ArrayList<>();
+ List<MdmAgentImportPreflightRespVO.Issue> warnings = new ArrayList<>();
+
+ if (StrUtil.isBlank(row.getCode())) {
+ blockers.add(issue("ROW_CODE_REQUIRED", "code", "鐗╂枡缂栫爜涓嶈兘涓虹┖", false));
+ } else if (duplicateCodes.contains(row.getCode().trim())) {
+ blockers.add(issue("DUPLICATE_CODE_IN_FILE", "code",
+ "鏂囦欢鍐呯墿鏂欑紪鐮侀噸澶嶏細" + row.getCode().trim(), false));
+ }
+ if (StrUtil.isBlank(row.getName())) {
+ blockers.add(issue("ROW_NAME_REQUIRED", "name", "鐗╂枡鍚嶇О涓嶈兘涓虹┖", false));
+ }
+ if (StrUtil.isNotBlank(row.getCategoryCode())
+ && !context.categoryCodeIdMap().containsKey(row.getCategoryCode())) {
+ blockers.add(issue("CATEGORY_NOT_FOUND", "categoryCode",
+ "鐗╂枡鍒嗙被缂栫爜涓嶅瓨鍦細" + row.getCategoryCode(), false));
+ }
+ if (StrUtil.isNotBlank(row.getUnitMeasureCode())
+ && !context.unitCodeIdMap().containsKey(row.getUnitMeasureCode())) {
+ blockers.add(issue("UNIT_NOT_FOUND", "unitMeasureCode",
+ "璁¢噺鍗曚綅缂栫爜涓嶅瓨鍦細" + row.getUnitMeasureCode(), false));
+ }
+
+ MdmItemRowResolution resolution = itemService.resolveImportRow(excelVO, context);
+ if (!resolution.isSuccess() && blockers.isEmpty()) {
+ // 鐞嗚涓婁笂闈㈢殑閫愰」鏍¢獙宸茶鐩栵紱鍏滃簳淇濇寔涓� Excel 瀵煎叆涓�鑷寸殑鏂囨
+ blockers.add(issue("ROW_INVALID", null, resolution.failureMessage(), false));
+ }
+
+ String action;
+ Long targetItemId = null;
+ if (!blockers.isEmpty()) {
+ action = "BLOCKED";
+ blockedCount++;
+ } else {
+ List<MdmItemDO> existing = itemService.getItemListByCode(row.getCode().trim());
+ if (!existing.isEmpty()) {
+ action = "SKIP";
+ targetItemId = existing.get(0).getId();
+ skipCount++;
+ warnings.add(issue("ITEM_CODE_EXISTS", "code", "鐗╂枡缂栫爜宸插瓨鍦紝鏈樁娈靛彧鏂板缓涓嶆洿鏂帮紝灏嗚烦杩囪琛�", false));
+ } else {
+ action = "CREATE";
+ createCount++;
+ }
+ }
+
+ rows.add(MdmAgentImportPreflightRespVO.Row.builder()
+ .candidateId(row.getCandidateId())
+ .rowNo(row.getRowNo())
+ .code(row.getCode())
+ .name(row.getName())
+ .action(action)
+ .resolved(resolve(row, context))
+ .blockers(blockers)
+ .warnings(warnings)
+ .targetItemId(targetItemId)
+ .build());
+ }
+
+ boolean canCommit = blockingItems.isEmpty() && blockedCount == 0;
+ String preflightHash = buildPreflightHash(reqVO, principal, attachment, sourceSha256,
+ syncMesRequested, rows);
+ return MdmAgentImportPreflightRespVO.builder()
+ .preflightHash(preflightHash)
+ .source(MdmAgentImportPreflightRespVO.Source.builder()
+ .attachmentId(attachment.attachmentId())
+ .sessionId(attachment.sessionId())
+ .sourceSha256(sourceSha256)
+ .proposalRevision(reqVO.getProposalRevision())
+ .build())
+ .authorization(MdmAgentImportPreflightRespVO.Authorization.builder()
+ .userId(principal.userId())
+ .permission(SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT)
+ .policyRevision(MdmAgentImportPolicy.POLICY_REVISION)
+ .build())
+ .policy(MdmAgentImportPreflightRespVO.Policy.builder()
+ .updateAllowed(false)
+ .mesSyncAllowed(policy.isAllowMesSync())
+ .maxRows(policy.getMaxRows())
+ .build())
+ .summary(MdmAgentImportPreflightRespVO.Summary.builder()
+ .total(rows.size())
+ .create(createCount)
+ .skip(skipCount)
+ .blocked(blockedCount)
+ .build())
+ .canCommit(canCommit)
+ .sideEffects(canCommit
+ ? (syncMesRequested
+ ? List.of("mdm_item.create:" + createCount, "mes_md_item.sync:" + createCount)
+ : List.of("mdm_item.create:" + createCount))
+ : List.of())
+ .rows(rows)
+ .blockingItems(blockingItems)
+ .build();
+ }
+
+ private static MdmItemImportExcelVO toExcelVO(MdmAgentImportPreflightReqVO.Row row) {
+ return MdmItemImportExcelVO.builder()
+ .code(row.getCode())
+ .name(row.getName())
+ .specification(row.getSpecification())
+ .categoryCode(row.getCategoryCode())
+ .unitMeasureCode(row.getUnitMeasureCode())
+ .build();
+ }
+
+ private static MdmAgentImportPreflightRespVO.Resolved resolve(MdmAgentImportPreflightReqVO.Row row,
+ MdmItemImportContext context) {
+ Long categoryId = StrUtil.isBlank(row.getCategoryCode()) ? null
+ : context.categoryCodeIdMap().get(row.getCategoryCode());
+ String categoryName = StrUtil.isBlank(row.getCategoryCode()) ? null
+ : context.categoryCodeNameMap().get(row.getCategoryCode());
+ Long unitMeasureId = StrUtil.isBlank(row.getUnitMeasureCode()) ? null
+ : context.unitCodeIdMap().get(row.getUnitMeasureCode());
+ String unitMeasureName = StrUtil.isBlank(row.getUnitMeasureCode()) ? null
+ : context.unitCodeNameMap().get(row.getUnitMeasureCode());
+ return MdmAgentImportPreflightRespVO.Resolved.builder()
+ .categoryId(categoryId).categoryName(categoryName)
+ .unitMeasureId(unitMeasureId).unitMeasureName(unitMeasureName)
+ .build();
+ }
+
+ private static MdmAgentImportPreflightRespVO.Issue issue(String code, String field, String message,
+ boolean retryable) {
+ return MdmAgentImportPreflightRespVO.Issue.builder()
+ .code(code).field(field).message(message).retryable(retryable).build();
+ }
+
+ /**
+ * 鎸夊浐瀹氬瓧娈甸『搴忔嫾瑁呰鑼� JSON 鍚庡彇 SHA-256锛氬悓涓�浠借鍒掑繀椤诲緱鍒板悓涓�涓� hash銆�
+ */
+ private static String buildPreflightHash(MdmAgentImportPreflightReqVO reqVO, AgentPrincipal principal,
+ SystemAgentAttachmentRef attachment, String sourceSha256,
+ boolean syncMesRequested, List<MdmAgentImportPreflightRespVO.Row> rows) {
+ Map<String, Object> canonical = new LinkedHashMap<>();
+ canonical.put("policy_revision", MdmAgentImportPolicy.POLICY_REVISION);
+ canonical.put("user_id", principal.userId());
+ canonical.put("attachment_id", attachment.attachmentId());
+ canonical.put("session_id", attachment.sessionId());
+ canonical.put("source_sha256", sourceSha256.toLowerCase(Locale.ROOT));
+ canonical.put("proposal_revision", StrUtil.blankToDefault(reqVO.getProposalRevision(), ""));
+ canonical.put("sync_mes", syncMesRequested);
+ List<Map<String, Object>> canonicalRows = new ArrayList<>();
+ for (MdmAgentImportPreflightRespVO.Row row : rows) {
+ Map<String, Object> item = new LinkedHashMap<>();
+ item.put("candidate_id", row.getCandidateId());
+ item.put("row_no", row.getRowNo());
+ item.put("code", row.getCode());
+ item.put("name", row.getName());
+ canonicalRows.add(item);
+ }
+ canonical.put("rows", canonicalRows);
+ 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 (com.fasterxml.jackson.core.JsonProcessingException ex) {
+ throw new IllegalStateException("棰勬鎽樿鐢熸垚澶辫触", ex);
+ }
+ }
+}
--
Gitblit v1.9.3