package com.ruoyi.web.controller.service;
|
|
import cn.hutool.core.collection.CollUtil;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.system.domain.SysVisitorRecord;
|
import com.ruoyi.system.mapper.SysVisitorRecordMapper;
|
import com.ruoyi.web.integration.dahua.DahuaVisitorAccessService;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
import java.util.Date;
|
import java.util.List;
|
import java.util.concurrent.TimeUnit;
|
|
@Service
|
@Slf4j
|
@Transactional(rollbackFor = Exception.class)
|
public class SysVisitorRecordService extends ServiceImpl<SysVisitorRecordMapper, SysVisitorRecord> {
|
|
@Autowired
|
private RedisTemplate<String, String> redisTemplate;
|
|
@Autowired
|
private DahuaVisitorAccessService dahuaVisitorAccessService;
|
|
@Autowired
|
private VisitorRegisterSyncService visitorRegisterSyncService;
|
|
private static final String REDIS_KEY_PREFIX = "visitor:expire:";
|
|
/**
|
* 登记访客时设置 Redis 过期 Key
|
*/
|
public void registerVisitor(SysVisitorRecord record) {
|
if (record.getValidEndTime() == null) {
|
throw new IllegalArgumentException("访客有效期结束时间不能为空");
|
}
|
if (!record.getValidEndTime().after(new Date())) {
|
log.warn("访客记录 {} 登记时已超期,不下发门禁权限", record.getRecordId());
|
autoSignOut(record);
|
return;
|
}
|
|
// 下发门禁权限后设置过期监听,确保登记与到期撤权使用同一条业务链路。
|
DahuaVisitorAccessService.OperationResult accessResult = dahuaVisitorAccessService.grant(record);
|
registerGrantRollbackCompensation(record, accessResult);
|
setRedisExpireKey(record);
|
|
log.info("访客 {} 登记成功,有效期至:{}", record.getRecordId(), record.getValidEndTime());
|
}
|
|
/**
|
* 设置 Redis 过期 Key
|
*/
|
private void setRedisExpireKey(SysVisitorRecord record) {
|
Date now = new Date();
|
Date validEndTime = record.getValidEndTime();
|
|
if (validEndTime == null) {
|
throw new IllegalArgumentException("访客有效期结束时间不能为空");
|
}
|
|
// 如果有效期结束时间小于当前时间,直接签离
|
if (validEndTime.before(now)) {
|
log.warn("访客记录 {} 已超期,直接签离", record.getRecordId());
|
autoSignOut(record);
|
return;
|
}
|
|
// 计算过期时间(毫秒)
|
long expireTime = validEndTime.getTime() - now.getTime();
|
String key = REDIS_KEY_PREFIX + record.getRecordId();
|
|
// 设置 Redis Key,过期时间为 validEndTime
|
redisTemplate.opsForValue().set(
|
key,
|
record.getRecordId().toString(),
|
expireTime,
|
TimeUnit.MILLISECONDS
|
);
|
|
log.info("设置Redis过期Key成功,Key:{},过期时间:{} 毫秒", key, expireTime);
|
}
|
|
/**
|
* 根据记录ID自动签离(由Redis过期事件触发)
|
*/
|
@Transactional
|
public void autoSignOutByRecordId(Long recordId) {
|
try {
|
// 1. 查询记录
|
SysVisitorRecord record = this.getById(recordId);
|
if (record == null) {
|
log.warn("访客记录不存在,ID:{}", recordId);
|
return;
|
}
|
|
// 2. 检查状态(只有 VISITING 状态才处理)
|
if (!"VISITING".equals(record.getStatus())) {
|
log.info("访客记录 {} 状态为 {},无需处理", recordId, record.getStatus());
|
return;
|
}
|
|
// 3. 检查是否真的超期(双重检查)
|
Date now = new Date();
|
if (!record.getValidEndTime().before(now)) {
|
log.info("访客记录 {} 有效期未结束,无需签离", recordId);
|
return;
|
}
|
|
// 4. 执行签离
|
autoSignOut(record);
|
|
// 5. 删除 Redis Key(防止重复触发)
|
redisTemplate.delete(REDIS_KEY_PREFIX + recordId);
|
|
log.info("Redis过期触发签离成功,记录ID:{},签离时间:{}", recordId, now);
|
|
} catch (Exception e) {
|
log.error("自动签离失败,记录ID:{}", recordId, e);
|
throw e;
|
}
|
}
|
|
/**
|
* 执行自动签离
|
*/
|
private void autoSignOut(SysVisitorRecord record) {
|
Date now = new Date();
|
revokeAccess(record);
|
record.setStatus("PENDING_CONFIRM");
|
record.setSignTime(now);
|
record.setRemark("逾期滞留,等待确认");
|
record.setUpdateTime(now);
|
this.updateById(record);
|
syncMiddlePlatformStatus(record);
|
}
|
|
/**
|
* 撤销访客在大华门禁设备上的人脸及人员权限。
|
*/
|
public void revokeAccess(SysVisitorRecord record) {
|
dahuaVisitorAccessService.revoke(record);
|
}
|
|
/**
|
* 同步访客状态到事件中心;失败不回滚本地签离结果。
|
*/
|
public void syncMiddlePlatformStatus(SysVisitorRecord record) {
|
visitorRegisterSyncService.syncVisitorStatus(record);
|
}
|
|
private void registerGrantRollbackCompensation(SysVisitorRecord record,
|
DahuaVisitorAccessService.OperationResult accessResult) {
|
if (accessResult.getSuccessCount() <= 0 || !TransactionSynchronizationManager.isSynchronizationActive()) {
|
return;
|
}
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
@Override
|
public void afterCompletion(int status) {
|
if (status != TransactionSynchronization.STATUS_ROLLED_BACK) {
|
return;
|
}
|
redisTemplate.delete(REDIS_KEY_PREFIX + record.getRecordId());
|
try {
|
dahuaVisitorAccessService.revoke(record);
|
log.info("访客登记事务回滚,大华门禁权限补偿撤销完成,recordId={}", record.getRecordId());
|
} catch (Exception e) {
|
log.error("访客登记事务回滚,大华门禁权限补偿撤销失败,recordId={}",
|
record.getRecordId(), e);
|
}
|
}
|
});
|
}
|
|
/**
|
* 更新访客记录(并重置 Redis 过期时间)
|
*/
|
public boolean updateVisitor(SysVisitorRecord record) {
|
// 1. 更新记录
|
boolean updated = this.updateById(record);
|
if (!updated) {
|
return false;
|
}
|
|
// 2. 如果状态变更为 VISITING,重新设置过期时间
|
if ("VISITING".equals(record.getStatus())) {
|
setRedisExpireKey(record);
|
}
|
|
// 3. 如果已签离,删除 Redis Key
|
if ("SIGNED_OUT".equals(record.getStatus()) ||
|
"OVERDUE_AUTO_SIGNED_OUT".equals(record.getStatus())) {
|
redisTemplate.delete(REDIS_KEY_PREFIX + record.getRecordId());
|
}
|
|
syncMiddlePlatformStatus(record);
|
|
return true;
|
}
|
|
/**
|
* 兜底方案:启动时检查所有超期记录
|
*/
|
@Transactional
|
public int checkAndAutoSignOutOnStartup() {
|
// 查询所有超期记录
|
LambdaQueryWrapper<SysVisitorRecord> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.eq(SysVisitorRecord::getStatus, "VISITING")
|
.lt(SysVisitorRecord::getValidEndTime, new Date());
|
|
List<SysVisitorRecord> overdueRecords = this.list(queryWrapper);
|
|
if (CollUtil.isEmpty(overdueRecords)) {
|
log.info("启动检查:没有超期访客记录");
|
return 0;
|
}
|
|
log.info("启动检查:发现 {} 条超期记录,开始处理", overdueRecords.size());
|
|
Date now = new Date();
|
int count = 0;
|
for (SysVisitorRecord record : overdueRecords) {
|
try {
|
revokeAccess(record);
|
record.setStatus("OVERDUE_AUTO_SIGNED_OUT");
|
record.setSignTime(now);
|
record.setRemark("系统自动签离(启动时检查)");
|
record.setUpdateTime(now);
|
this.updateById(record);
|
syncMiddlePlatformStatus(record);
|
|
// 删除 Redis Key
|
redisTemplate.delete(REDIS_KEY_PREFIX + record.getRecordId());
|
count++;
|
} catch (Exception e) {
|
log.error("启动检查处理记录失败,ID:{}", record.getRecordId(), e);
|
}
|
}
|
|
log.info("启动检查完成,处理 {} 条超期记录", count);
|
return count;
|
}
|
}
|