修复门禁:阻断不确定下发重试并保护迟到结果
This commit is contained in:
1 parent
1d2ca54d26
commit
71bde5e674
4 files changed
+135
-35
No files matched your search
@@ -232,35 +232,30 @@ public class TrainDormAuth extends BaseEntity
|
||||
this.authEnd = authEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Legacy field. auth_status 是唯一业务真相源 (Section 20)。
|
||||
*/
|
||||
@Deprecated
|
||||
public Integer getFaceSyncStatus()
|
||||
{
|
||||
return faceSyncStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Legacy field. auth_status 是唯一业务真相源 (Section 20)。
|
||||
*/
|
||||
@Deprecated
|
||||
public void setFaceSyncStatus(Integer faceSyncStatus)
|
||||
{
|
||||
this.faceSyncStatus = faceSyncStatus;
|
||||
if (faceSyncStatus != null) {
|
||||
switch (faceSyncStatus) {
|
||||
case 0:
|
||||
if (!AUTH_STATUS_GRANTING.equals(this.authStatus)) {
|
||||
this.authStatus = AUTH_STATUS_PENDING_GRANT;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (!AUTH_STATUS_PENDING_REVOKE.equals(this.authStatus) && !AUTH_STATUS_REVOKING.equals(this.authStatus)) {
|
||||
this.authStatus = AUTH_STATUS_ACTIVE;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
this.authStatus = AUTH_STATUS_ERROR;
|
||||
break;
|
||||
case 3:
|
||||
this.authStatus = AUTH_STATUS_REVOKED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (STATUS_ACTIVE.equals(faceSyncStatus)) {
|
||||
this.authStatus = AUTH_STATUS_ACTIVE;
|
||||
} else if (STATUS_PENDING.equals(faceSyncStatus)) {
|
||||
this.authStatus = AUTH_STATUS_PENDING_GRANT;
|
||||
} else if (STATUS_FAILED.equals(faceSyncStatus)) {
|
||||
this.authStatus = AUTH_STATUS_ERROR;
|
||||
} else if (STATUS_REVOKED.equals(faceSyncStatus)) {
|
||||
this.authStatus = AUTH_STATUS_REVOKED;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
@@ -98,6 +98,18 @@ public interface ITrainDormAuthService
|
||||
*/
|
||||
public int updateAuthStatus(Long authId, String authStatus, Long operationId, String operator);
|
||||
|
||||
/**
|
||||
* 更新业务授权状态、当前驱动的外部操作ID及错误信息
|
||||
*
|
||||
* @param authId 授权主键
|
||||
* @param authStatus 业务授权状态
|
||||
* @param operationId 外部操作ID
|
||||
* @param lastError 错误原因
|
||||
* @param operator 操作人
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int updateAuthStatus(Long authId, String authStatus, Long operationId, String lastError, String operator);
|
||||
|
||||
/**
|
||||
* 由对账轮询器回调更新业务授权状态 (内置 Stale Operation 防迟到覆盖保护)
|
||||
* 只有当 operationId 与授权记录当前最新的 latestOperationId 一致时,才允许推进状态
|
||||
@@ -110,4 +122,17 @@ public interface ITrainDormAuthService
|
||||
* @return 影响行数 (若被防迟到保护拦截返回 0)
|
||||
*/
|
||||
public int updateAuthStatusByPoller(Long authId, String authStatus, Long operationId, String lastError, String operator);
|
||||
|
||||
/**
|
||||
* 由对账轮询器回调更新业务授权状态 (内置 Stale Operation 防迟到覆盖保护与状态兼容矩阵)
|
||||
*
|
||||
* @param authId 授权主键
|
||||
* @param authStatus 目标业务授权状态
|
||||
* @param operationId 触发更新的操作流水ID
|
||||
* @param operationType 操作类型 (GRANT, REVOKE 等)
|
||||
* @param lastError 错误消息摘要
|
||||
* @param operator 操作人
|
||||
* @return 影响行数 (若被防迟到保护拦截返回 0)
|
||||
*/
|
||||
public int updateAuthStatusByPoller(Long authId, String authStatus, Long operationId, String operationType, String lastError, String operator);
|
||||
}
|
||||
+74
-14
@@ -2,6 +2,8 @@ package com.ruoyi.train.service.impl;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -9,6 +11,7 @@ import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.train.domain.TrainAccommodation;
|
||||
import com.ruoyi.train.domain.TrainDormAuth;
|
||||
import com.ruoyi.train.domain.TrainExternalOperation;
|
||||
import com.ruoyi.train.mapper.TrainAccommodationMapper;
|
||||
import com.ruoyi.train.mapper.TrainDormAuthMapper;
|
||||
import com.ruoyi.train.service.ITrainDormAuthService;
|
||||
@@ -22,6 +25,8 @@ import com.ruoyi.train.service.ITrainDormAuthService;
|
||||
@Service
|
||||
public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(TrainDormAuthServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private TrainDormAuthMapper trainDormAuthMapper;
|
||||
|
||||
@@ -196,16 +201,14 @@ public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// 门禁授权生效保护:生效中或授权中状态,禁止直接清理,必须走标准注销流程
|
||||
if (TrainDormAuth.STATUS_ACTIVE.equals(exist.getFaceSyncStatus())
|
||||
|| TrainDormAuth.AUTH_STATUS_ACTIVE.equals(exist.getAuthStatus())
|
||||
// 门禁授权生效保护:生效中或授权中状态,禁止直接清理,必须走标准注销流程 (BLK-02 / Single Source of Truth)
|
||||
if (TrainDormAuth.AUTH_STATUS_ACTIVE.equals(exist.getAuthStatus())
|
||||
|| TrainDormAuth.AUTH_STATUS_GRANTING.equals(exist.getAuthStatus()))
|
||||
{
|
||||
throw new ServiceException("当前门禁授权处于生效或授权中状态,请先执行注销后再处理");
|
||||
}
|
||||
// 门禁审计链完整性保护:已注销、注销流转中或已关联外部操作流水的历史授权,严禁物理删除
|
||||
if (TrainDormAuth.STATUS_REVOKED.equals(exist.getFaceSyncStatus())
|
||||
|| TrainDormAuth.AUTH_STATUS_REVOKED.equals(exist.getAuthStatus())
|
||||
if (TrainDormAuth.AUTH_STATUS_REVOKED.equals(exist.getAuthStatus())
|
||||
|| TrainDormAuth.AUTH_STATUS_PENDING_REVOKE.equals(exist.getAuthStatus())
|
||||
|| TrainDormAuth.AUTH_STATUS_REVOKING.equals(exist.getAuthStatus())
|
||||
|| exist.getLatestOperationId() != null
|
||||
@@ -213,9 +216,8 @@ public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
{
|
||||
throw new ServiceException("该门禁授权已进入授权生命周期或产生外部审计流水,禁止物理删除历史凭证");
|
||||
}
|
||||
// 安全底线:仅允许清理尚未执行过任何下发动作的初始待授权草稿 (STATUS_PENDING / AUTH_STATUS_PENDING_GRANT)
|
||||
if ((exist.getFaceSyncStatus() != null && !TrainDormAuth.STATUS_PENDING.equals(exist.getFaceSyncStatus()))
|
||||
|| (exist.getAuthStatus() != null && !TrainDormAuth.AUTH_STATUS_PENDING_GRANT.equals(exist.getAuthStatus())))
|
||||
// 安全底线:仅允许清理尚未执行过任何下发动作的初始待授权草稿 (AUTH_STATUS_PENDING_GRANT)
|
||||
if (exist.getAuthStatus() != null && !TrainDormAuth.AUTH_STATUS_PENDING_GRANT.equals(exist.getAuthStatus()))
|
||||
{
|
||||
throw new ServiceException("当前门禁授权非待下发草稿状态,禁止物理删除");
|
||||
}
|
||||
@@ -272,6 +274,13 @@ public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateAuthStatus(Long authId, String authStatus, Long operationId, String operator)
|
||||
{
|
||||
return updateAuthStatus(authId, authStatus, operationId, null, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateAuthStatus(Long authId, String authStatus, Long operationId, String lastError, String operator)
|
||||
{
|
||||
TrainDormAuth exist = trainDormAuthMapper.selectTrainDormAuthByAuthId(authId);
|
||||
if (exist == null)
|
||||
@@ -283,6 +292,10 @@ public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
{
|
||||
exist.setLatestOperationId(operationId);
|
||||
}
|
||||
if (lastError != null)
|
||||
{
|
||||
exist.setLastError(lastError);
|
||||
}
|
||||
exist.setOperationVersion((exist.getOperationVersion() != null ? exist.getOperationVersion() : 1) + 1);
|
||||
exist.setUpdateBy(operator);
|
||||
exist.setUpdateTime(DateUtils.getNowDate());
|
||||
@@ -297,6 +310,19 @@ public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateAuthStatusByPoller(Long authId, String authStatus, Long operationId, String lastError, String operator)
|
||||
{
|
||||
String derivedType = null;
|
||||
if (TrainDormAuth.AUTH_STATUS_ACTIVE.equals(authStatus)) {
|
||||
derivedType = TrainExternalOperation.TYPE_GRANT;
|
||||
} else if (TrainDormAuth.AUTH_STATUS_REVOKED.equals(authStatus)) {
|
||||
derivedType = TrainExternalOperation.TYPE_REVOKE;
|
||||
}
|
||||
return updateAuthStatusByPoller(authId, authStatus, operationId, derivedType, lastError, operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateAuthStatusByPoller(Long authId, String targetAuthStatus, Long operationId, String operationType, String lastError, String operator)
|
||||
{
|
||||
TrainDormAuth exist = trainDormAuthMapper.selectTrainDormAuthByAuthId(authId);
|
||||
if (exist == null)
|
||||
@@ -304,15 +330,49 @@ public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 核心防迟到保护 (STALE_OPERATION_RESULT_GUARD)
|
||||
// 只有当传入的 operationId 与当前记录绑定的 latestOperationId 相符时才更新
|
||||
if (operationId != null && exist.getLatestOperationId() != null && !operationId.equals(exist.getLatestOperationId()))
|
||||
// 1. 核心防迟到保护:operationId 必须与当前记录权威 latestOperationId 完全一致 (BLK-02)
|
||||
if (operationId == null || exist.getLatestOperationId() == null || !operationId.equals(exist.getLatestOperationId()))
|
||||
{
|
||||
// 说明已被后续发起的操作替代,忽略此迟到结果
|
||||
log.warn("Poller update rejected (Stale operation guard): authId={}, pollerOpId={}, latestOpId={}",
|
||||
authId, operationId, exist.getLatestOperationId());
|
||||
return 0;
|
||||
}
|
||||
|
||||
exist.setAuthStatus(authStatus);
|
||||
String currentStatus = exist.getAuthStatus();
|
||||
|
||||
// 2. 状态兼容矩阵校验 (State Compatibility Matrix Guard: BLK-02)
|
||||
if (TrainDormAuth.AUTH_STATUS_ACTIVE.equals(targetAuthStatus))
|
||||
{
|
||||
// GRANT SUCCESS: 仅允许 GRANTING -> ACTIVE。
|
||||
// 其它状态 (PENDING_REVOKE, REVOKING, REVOKED, ERROR, PENDING_GRANT) 严禁被推进为 ACTIVE!
|
||||
if (!TrainDormAuth.AUTH_STATUS_GRANTING.equals(currentStatus))
|
||||
{
|
||||
log.warn("Late/Invalid GRANT SUCCESS rejected for authId {}: currentStatus={}, prohibited from advancing to ACTIVE",
|
||||
authId, currentStatus);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (TrainDormAuth.AUTH_STATUS_REVOKED.equals(targetAuthStatus))
|
||||
{
|
||||
// REVOKE SUCCESS: 仅允许 REVOKING -> REVOKED (兼容 PENDING_REVOKE)
|
||||
if (!TrainDormAuth.AUTH_STATUS_REVOKING.equals(currentStatus) && !TrainDormAuth.AUTH_STATUS_PENDING_REVOKE.equals(currentStatus))
|
||||
{
|
||||
log.warn("Late/Invalid REVOKE SUCCESS rejected for authId {}: currentStatus={}, prohibited from advancing to REVOKED",
|
||||
authId, currentStatus);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (TrainDormAuth.AUTH_STATUS_ERROR.equals(targetAuthStatus))
|
||||
{
|
||||
// 终态保护:已注销 (REVOKED) 不允许因历史流水失败退回到 ERROR
|
||||
if (TrainDormAuth.AUTH_STATUS_REVOKED.equals(currentStatus))
|
||||
{
|
||||
log.warn("Poller ERROR rejected for authId {}: already in terminal REVOKED state", authId);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
exist.setAuthStatus(targetAuthStatus);
|
||||
if (lastError != null)
|
||||
{
|
||||
exist.setLastError(lastError);
|
||||
@@ -320,7 +380,7 @@ public class TrainDormAuthServiceImpl implements ITrainDormAuthService
|
||||
exist.setUpdateBy(operator != null ? operator : "DmsPoller");
|
||||
exist.setUpdateTime(DateUtils.getNowDate());
|
||||
int rows = trainDormAuthMapper.updateTrainDormAuth(exist);
|
||||
if (rows > 0 && lastError == null && (TrainDormAuth.AUTH_STATUS_ACTIVE.equals(authStatus) || TrainDormAuth.AUTH_STATUS_REVOKED.equals(authStatus)))
|
||||
if (rows > 0 && lastError == null && (TrainDormAuth.AUTH_STATUS_ACTIVE.equals(targetAuthStatus) || TrainDormAuth.AUTH_STATUS_REVOKED.equals(targetAuthStatus)))
|
||||
{
|
||||
trainDormAuthMapper.clearLastError(authId);
|
||||
}
|
||||
|
||||
+20
@@ -561,6 +561,26 @@ public class DormAuthAlignmentTest
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countActiveByDormIdAndBedNo(String dormId, String bedNo, Long excludeAccommodationId)
|
||||
{
|
||||
if (dormId == null || bedNo == null || bedNo.trim().isEmpty()) return 0;
|
||||
int count = 0;
|
||||
for (TrainAccommodation a : store.values())
|
||||
{
|
||||
if (excludeAccommodationId != null && excludeAccommodationId.equals(a.getAccommodationId())) continue;
|
||||
if (dormId.equals(a.getDormId()) && bedNo.equals(a.getBedNo()))
|
||||
{
|
||||
if (TrainAccommodation.STATUS_PREALLOCATED.equals(a.getAccommodationStatus())
|
||||
|| TrainAccommodation.STATUS_CHECKED_IN.equals(a.getAccommodationStatus()))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertTrainAccommodation(TrainAccommodation accommodation)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user