From f8ca87cfabbe0b17963eb31ffbbc8acb4704d789 Mon Sep 17 00:00:00 2001 From: wangzhibiao Date: Mon, 21 Sep 2026 12:51:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=97=A8=E7=A6=81=EF=BC=9A?= =?UTF-8?q?=E8=A1=A5=E9=BD=90=E5=A4=9A=E6=8E=88=E6=9D=83=E6=92=A4=E9=94=80?= =?UTF-8?q?=E4=B8=8E=E4=BA=8B=E5=8A=A1=E8=A1=A5=E5=81=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dms/poller/DmsOperationPoller.java | 105 +++++++++++++++++- .../impl/TrainAccommodationServiceImpl.java | 14 --- .../train/TrainExternalOperationMapper.xml | 4 +- 3 files changed, 103 insertions(+), 20 deletions(-) diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/integration/dms/poller/DmsOperationPoller.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/integration/dms/poller/DmsOperationPoller.java index 99bc45bbe..e274e3e8e 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/integration/dms/poller/DmsOperationPoller.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/integration/dms/poller/DmsOperationPoller.java @@ -6,10 +6,13 @@ import com.ruoyi.train.integration.dms.client.DmsOperationStatusClient; import com.ruoyi.train.integration.dms.config.DmsProperties; import com.ruoyi.train.integration.dms.dto.DmsOperationStatusRequest; import com.ruoyi.train.integration.dms.dto.DmsOperationStatusResponse; +import com.ruoyi.train.integration.dms.dto.DmsPasswordDeleteResponse; import com.ruoyi.train.integration.dms.exception.DmsAuthenticationException; import com.ruoyi.train.integration.dms.exception.DmsBusinessException; import com.ruoyi.train.integration.dms.exception.DmsTransportException; import com.ruoyi.train.integration.dms.gateway.DmsOperationStatusMapper; +import com.ruoyi.train.integration.dms.gateway.ExternalCredentialGateway; +import com.ruoyi.train.mapper.TrainDormAuthMapper; import com.ruoyi.train.service.IExternalOperationService; import com.ruoyi.train.service.ITrainDormAuthService; import com.ruoyi.train.service.impl.ExternalOperationServiceImpl; @@ -56,6 +59,10 @@ public class DmsOperationPoller { private DmsOperationStatusClient operationStatusClient; @org.springframework.beans.factory.annotation.Autowired(required = false) private ITrainDormAuthService dormAuthService; + @org.springframework.beans.factory.annotation.Autowired(required = false) + private ExternalCredentialGateway credentialGateway; + @org.springframework.beans.factory.annotation.Autowired(required = false) + private TrainDormAuthMapper dormAuthMapper; private final Set inFlightOperationIds = ConcurrentHashMap.newKeySet(); private Clock clock = Clock.systemUTC(); @@ -66,18 +73,53 @@ public class DmsOperationPoller { public DmsOperationPoller(DmsProperties dmsProperties, IExternalOperationService externalOperationService, DmsOperationStatusClient operationStatusClient) { - this(dmsProperties, externalOperationService, operationStatusClient, null); + this(dmsProperties, externalOperationService, operationStatusClient, null, null, null); + } + + public DmsOperationPoller(DmsProperties dmsProperties, + IExternalOperationService externalOperationService, + DmsOperationStatusClient operationStatusClient, + ITrainDormAuthService dormAuthService) { + this(dmsProperties, externalOperationService, operationStatusClient, dormAuthService, null, null); } @org.springframework.beans.factory.annotation.Autowired public DmsOperationPoller(DmsProperties dmsProperties, IExternalOperationService externalOperationService, DmsOperationStatusClient operationStatusClient, - @org.springframework.beans.factory.annotation.Autowired(required = false) ITrainDormAuthService dormAuthService) { + @org.springframework.beans.factory.annotation.Autowired(required = false) ITrainDormAuthService dormAuthService, + @org.springframework.beans.factory.annotation.Autowired(required = false) ExternalCredentialGateway credentialGateway, + @org.springframework.beans.factory.annotation.Autowired(required = false) TrainDormAuthMapper dormAuthMapper) { this.dmsProperties = dmsProperties; this.externalOperationService = externalOperationService; this.operationStatusClient = operationStatusClient; this.dormAuthService = dormAuthService; + this.credentialGateway = credentialGateway; + this.dormAuthMapper = dormAuthMapper; + } + + public void setCredentialGateway(ExternalCredentialGateway credentialGateway) { + this.credentialGateway = credentialGateway; + } + + public void setDormAuthMapper(TrainDormAuthMapper dormAuthMapper) { + this.dormAuthMapper = dormAuthMapper; + } + + public void setDmsProperties(DmsProperties dmsProperties) { + this.dmsProperties = dmsProperties; + } + + public void setExternalOperationService(IExternalOperationService externalOperationService) { + this.externalOperationService = externalOperationService; + } + + public void setOperationStatusClient(DmsOperationStatusClient operationStatusClient) { + this.operationStatusClient = operationStatusClient; + } + + public void setDormAuthService(ITrainDormAuthService dormAuthService) { + this.dormAuthService = dormAuthService; } public void setClock(Clock clock) { @@ -144,6 +186,11 @@ public class DmsOperationPoller { } private void processSingleOperation(TrainExternalOperation op, Instant now) { + if (TrainExternalOperation.STATUS_CREATED.equals(op.getOperationStatus())) { + processCreatedOperation(op, now); + return; + } + String extOpIdStr = op.getExternalOperationId(); if (extOpIdStr == null || extOpIdStr.trim().isEmpty()) { // 未关联外部操作流水号,无法直接对账 @@ -317,11 +364,61 @@ public class DmsOperationPoller { } } + /** + * 崩溃补偿:处理处于 CREATED 状态且已到重试时间、尚未实际派发的撤销操作 (Req 18) + */ + private void processCreatedOperation(TrainExternalOperation op, Instant now) { + if (!TrainExternalOperation.TYPE_REVOKE.equals(op.getOperationType())) { + // Package A: 仅支持自动补偿撤销操作,Grant 由管理员交互手动下发 + log.info("Operation {} is in CREATED state with type {}, skipping poller auto-dispatch", op.getOperationId(), op.getOperationType()); + return; + } + + if (dormAuthMapper == null || credentialGateway == null) { + log.warn("Poller dependencies missing for processing CREATED operation {}", op.getOperationId()); + return; + } + + TrainDormAuth auth = dormAuthMapper.selectTrainDormAuthByAuthId(op.getDoorAuthId()); + if (auth == null) { + handlePollError(op, now, ERROR_CATEGORY_VENDOR_BIZ, "Associated auth not found: " + op.getDoorAuthId(), true); + return; + } + + if (auth.getExternalAuthId() == null || auth.getExternalAuthId().trim().isEmpty()) { + handlePollError(op, now, ERROR_CATEGORY_VENDOR_BIZ, "UNRESOLVABLE_EXTERNAL_CREDENTIAL: Missing externalAuthId", true); + return; + } + + Long externalKeyId; + try { + externalKeyId = Long.parseLong(auth.getExternalAuthId().trim()); + } catch (NumberFormatException e) { + handlePollError(op, now, ERROR_CATEGORY_VENDOR_BIZ, "Invalid externalAuthId format: " + auth.getExternalAuthId(), true); + return; + } + + try { + DmsPasswordDeleteResponse resp = credentialGateway.revokeOnlinePassword(externalKeyId); + String extOpIdStr = resp.getOperationId() != null ? String.valueOf(resp.getOperationId()) : null; + + if (externalOperationService != null) { + externalOperationService.bindExternalReference(op.getOperationId(), extOpIdStr, null, null, "DmsPollerCrashCompensation"); + externalOperationService.transitStatus(op.getOperationId(), TrainExternalOperation.STATUS_ACCEPTED, null, null, "DmsPollerCrashCompensation"); + } + log.info("DmsPoller successfully compensated CREATED revoke operation {}: extOpId={}", op.getOperationId(), extOpIdStr); + } catch (Exception e) { + handlePollError(op, now, ERROR_CATEGORY_NETWORK, sanitize(e.getMessage()), false); + } + } + private String sanitize(String str) { if (str == null) { return null; } - return str.replaceAll("(?i)(password|token|secret|appsecret)=[^&\\s]+", "$1=[PROTECTED]") - .replaceAll("(?i)(\"password\"\\s*:\\s*\")[^\"]+(\")", "$1[PROTECTED]$2"); + String sanitized = str.replaceAll("(?i)(password|token|secret|appsecret)=[^&\\s]+", "$1=[PROTECTED]") + .replaceAll("(?i)(\"password\"\\s*:\\s*\")[^\"]+(\")", "$1[PROTECTED]$2"); + sanitized = sanitized.replaceAll("(? - WHERE operation_status IN ('ACCEPTED', 'PENDING', 'RETRY_WAIT') - AND next_poll_time <= #{maxPollTime} + WHERE operation_status IN ('CREATED', 'ACCEPTED', 'PENDING', 'RETRY_WAIT') + AND (next_poll_time IS NULL OR next_poll_time <= #{maxPollTime}) AND vendor_code = #{vendorCode}