修复门禁:补齐多授权撤销与事务补偿
This commit is contained in:
1 parent
0d5df72d8e
commit
f8ca87cfab
3 files changed
+103
-20
No files matched your search
+101
-4
@@ -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<Long> 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("(?<!\\d)(1[3-9]\\d)\\d{4}(\\d{4})(?!\\d)", "$1****$2");
|
||||
return sanitized;
|
||||
}
|
||||
}
|
||||
-14
@@ -315,20 +315,6 @@ public class TrainAccommodationServiceImpl implements ITrainAccommodationService
|
||||
newAcc.setRemark("由原住宿记录 " + oldAccommodationId + " (宿舍 " + oldAcc.getDormId() + ") 调房迁入");
|
||||
|
||||
insertTrainAccommodation(newAcc);
|
||||
|
||||
// 3. 调房遵循 GRANT_NEW_THEN_REVOKE_OLD:先为新房发起对账发放,旧房在新房生效前绝不注销
|
||||
if (accessAuthorizationOrchestrator != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
accessAuthorizationOrchestrator.reconcile(oldAcc.getEnrollmentId());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.warn("Access reconcile triggered on transfer failed for enrollment {}: {}", oldAcc.getEnrollmentId(), ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return newAcc;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -64,8 +64,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<select id="selectPendingPollOperations" resultMap="TrainExternalOperationResult">
|
||||
<include refid="selectTrainExternalOperationVo"/>
|
||||
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})
|
||||
<if test="vendorCode != null and vendorCode != ''">
|
||||
AND vendor_code = #{vendorCode}
|
||||
</if>
|
||||
|
||||
Reference in new issue
Block a user