package com.ruoyi.system.service.impl;
|
|
import java.net.InetAddress;
|
import java.util.Date;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.regex.Pattern;
|
import java.time.Instant;
|
import java.util.Map;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.system.domain.TCollector;
|
import com.ruoyi.system.mapper.TCollectorMapper;
|
import com.ruoyi.system.service.ITCollectorService;
|
import com.battery.device.discovery.DeviceDiscoveryCandidateStore;
|
import com.battery.device.discovery.DeviceDiscoveryCandidateStore.CollectorCandidate;
|
|
/**
|
* 采集器 Service 实现。
|
*
|
* @author ruoyi
|
*/
|
@Service
|
public class TCollectorServiceImpl implements ITCollectorService
|
{
|
private static final String ENABLED = "0";
|
private static final String DISABLED = "1";
|
private static final String OFFLINE = "0";
|
private static final String ONLINE = "1";
|
private static final String IDLE = "0";
|
private static final String TESTING = "1";
|
private static final Pattern IPV4_PATTERN = Pattern.compile(
|
"^(25[0-5]|2[0-4]\\d|1?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}$");
|
private static final Pattern MAC_PATTERN = Pattern.compile("^[0-9A-F]{2}(:[0-9A-F]{2}){5}$");
|
|
private final TCollectorMapper collectorMapper;
|
private final DeviceDiscoveryCandidateStore candidateStore;
|
|
public TCollectorServiceImpl(TCollectorMapper collectorMapper, DeviceDiscoveryCandidateStore candidateStore)
|
{
|
this.collectorMapper = collectorMapper;
|
this.candidateStore = candidateStore;
|
}
|
|
/**
|
* 查询未删除采集器,不存在时抛出业务异常。
|
*/
|
@Override
|
public TCollector selectCollectorById(Long collectorId)
|
{
|
if (collectorId == null)
|
{
|
throw new ServiceException("采集器ID不能为空");
|
}
|
TCollector collector = collectorMapper.selectCollectorById(collectorId);
|
if (collector == null)
|
{
|
throw new ServiceException("采集器不存在");
|
}
|
return applyRealtimeState(collector, candidatesByCode());
|
}
|
|
/**
|
* 按设备、创建时间区间和状态查询采集器列表。
|
*/
|
@Override
|
public List<TCollector> selectCollectorList(TCollector collector)
|
{
|
if (collector == null)
|
{
|
collector = new TCollector();
|
}
|
collector.setDevice(trimToNull(collector.getDevice()));
|
collector.setBeginCreateTime(trimToNull(collector.getBeginCreateTime()));
|
collector.setEndCreateTime(trimToNull(collector.getEndCreateTime()));
|
String requestedOnlineStatus = collector.getOnlineStatus();
|
validateOptionalStatus(requestedOnlineStatus, "在线状态");
|
collector.setOnlineStatus(null);
|
validateOptionalStatus(collector.getTestStatus(), "测试状态");
|
validateOptionalStatus(collector.getStatus(), "状态");
|
Map<String, CollectorCandidate> candidates = candidatesByCode();
|
return collectorMapper.selectCollectorList(collector).stream()
|
.map(item -> applyRealtimeState(item, candidates))
|
.filter(item -> StringUtils.isEmpty(requestedOnlineStatus)
|
|| requestedOnlineStatus.equals(item.getOnlineStatus()))
|
.toList();
|
}
|
|
/**
|
* 新增采集器并初始化运行状态。
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int insertCollector(TCollector collector)
|
{
|
if (collector == null) throw new ServiceException("采集器信息不能为空");
|
collector.setCollectorCode(trimToNull(collector.getCollectorCode()));
|
CollectorCandidate candidate = candidateStore.findCollector(collector.getCollectorCode(), Instant.now())
|
.filter(CollectorCandidate::online)
|
.orElseThrow(() -> new ServiceException("请先选择在线的发现候选设备"));
|
collector.setIpAddress(candidate.ip());
|
collector.setMacAddress(candidate.mac());
|
collector.setChannelCount(candidate.channelCount());
|
validateCollector(collector, false);
|
collector.setOnlineStatus(OFFLINE);
|
collector.setTestStatus(IDLE);
|
collector.setCurrentTest(null);
|
collector.setCurrentSampleName(null);
|
if (StringUtils.isEmpty(collector.getStatus()))
|
{
|
collector.setStatus(ENABLED);
|
}
|
else
|
{
|
validateStatus(collector.getStatus(), "状态");
|
}
|
collector.setDelFlag("0");
|
collector.setCreateTime(new Date());
|
return collectorMapper.insertCollector(collector);
|
}
|
|
/**
|
* 编辑采集器基础资料和启禁用状态,不覆盖设备运行状态。
|
*/
|
@Override
|
public int updateCollector(TCollector collector)
|
{
|
validateCollector(collector, true);
|
validateStatus(collector.getStatus(), "状态");
|
if (DISABLED.equals(collector.getStatus()))
|
{
|
validateCollectorNotTesting(selectCollectorById(collector.getCollectorId()), "禁用");
|
}
|
collector.setUpdateTime(new Date());
|
return collectorMapper.updateCollector(collector);
|
}
|
|
/**
|
* 启用或禁用采集器;测试中的采集器禁止禁用。
|
*/
|
@Override
|
public int updateCollectorStatus(TCollector collector)
|
{
|
if (collector == null || collector.getCollectorId() == null)
|
{
|
throw new ServiceException("采集器ID不能为空");
|
}
|
validateStatus(collector.getStatus(), "状态");
|
TCollector current = selectCollectorById(collector.getCollectorId());
|
if (DISABLED.equals(collector.getStatus()))
|
{
|
validateCollectorNotTesting(current, "禁用");
|
}
|
if (collector.getStatus().equals(current.getStatus()))
|
{
|
return 1;
|
}
|
collector.setUpdateTime(new Date());
|
return collectorMapper.updateCollectorStatus(collector);
|
}
|
|
/** 查询全部未删除采集器的设备编码,供发现候选排除已登记设备。 */
|
@Override
|
public Set<String> selectRegisteredCollectorCodes()
|
{
|
return Set.copyOf(collectorMapper.selectRegisteredCollectorCodes());
|
}
|
|
/**
|
* 先校验全部记录,再在同一事务中执行批量逻辑删除。
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int deleteCollectorByIds(Long[] collectorIds, String updateBy)
|
{
|
if (collectorIds == null || collectorIds.length == 0)
|
{
|
throw new ServiceException("请选择要删除的采集器");
|
}
|
for (Long collectorId : collectorIds)
|
{
|
validateCollectorNotTesting(selectCollectorById(collectorId), "删除");
|
}
|
int rows = 0;
|
for (Long collectorId : collectorIds)
|
{
|
rows += collectorMapper.deleteCollectorById(collectorId, updateBy);
|
}
|
return rows;
|
}
|
|
/**
|
* 校验并规范化采集器基础资料,同时检查唯一性。
|
*/
|
private void validateCollector(TCollector collector, boolean update)
|
{
|
if (collector == null)
|
{
|
throw new ServiceException("采集器信息不能为空");
|
}
|
if (update)
|
{
|
selectCollectorById(collector.getCollectorId());
|
}
|
collector.setCollectorCode(trimToNull(collector.getCollectorCode()));
|
collector.setIpAddress(trimToNull(collector.getIpAddress()));
|
collector.setMacAddress(normalizeMacAddress(collector.getMacAddress()));
|
collector.setCollectorName(trimToNull(collector.getCollectorName()));
|
collector.setManufacturer(trimToNull(collector.getManufacturer()));
|
collector.setModel(trimToNull(collector.getModel()));
|
collector.setRemark(trimToNull(collector.getRemark()));
|
|
if (StringUtils.isEmpty(collector.getCollectorCode()))
|
{
|
throw new ServiceException("设备编码不能为空");
|
}
|
if (StringUtils.isEmpty(collector.getCollectorName()))
|
{
|
throw new ServiceException("采集器名称不能为空");
|
}
|
validateIpAddress(collector.getIpAddress());
|
if (collector.getChannelCount() == null || collector.getChannelCount() < 1
|
|| collector.getChannelCount() > 10000)
|
{
|
throw new ServiceException("通道数必须在1到10000之间");
|
}
|
validateUnique(collector);
|
}
|
|
/**
|
* 校验设备编码和 MAC 地址在未删除数据中唯一。
|
*/
|
private void validateUnique(TCollector collector)
|
{
|
TCollector sameCode = collectorMapper.selectCollectorByCode(collector.getCollectorCode());
|
if (isDifferentCollector(sameCode, collector.getCollectorId()))
|
{
|
throw new ServiceException("设备编码已存在");
|
}
|
TCollector sameMac = collectorMapper.selectCollectorByMacAddress(collector.getMacAddress());
|
if (isDifferentCollector(sameMac, collector.getCollectorId()))
|
{
|
throw new ServiceException("MAC地址已存在");
|
}
|
}
|
|
/**
|
* 判断唯一性查询结果是否属于其他采集器。
|
*/
|
private boolean isDifferentCollector(TCollector existing, Long collectorId)
|
{
|
return existing != null && (collectorId == null || !collectorId.equals(existing.getCollectorId()));
|
}
|
|
/**
|
* 规范化并校验 MAC 地址。
|
*/
|
private String normalizeMacAddress(String macAddress)
|
{
|
String normalized = trimToNull(macAddress);
|
if (normalized == null)
|
{
|
throw new ServiceException("MAC地址不能为空");
|
}
|
normalized = normalized.replace('-', ':').toUpperCase();
|
if (!MAC_PATTERN.matcher(normalized).matches())
|
{
|
throw new ServiceException("MAC地址格式不正确");
|
}
|
return normalized;
|
}
|
|
/**
|
* 校验 IPv4 或 IPv6 地址,不接受主机名。
|
*/
|
private void validateIpAddress(String ipAddress)
|
{
|
if (StringUtils.isEmpty(ipAddress))
|
{
|
throw new ServiceException("IP地址不能为空");
|
}
|
if (IPV4_PATTERN.matcher(ipAddress).matches())
|
{
|
return;
|
}
|
if (!ipAddress.contains(":"))
|
{
|
throw new ServiceException("IP地址格式不正确");
|
}
|
try
|
{
|
InetAddress address = InetAddress.getByName(ipAddress);
|
if (address.getAddress().length != 16)
|
{
|
throw new ServiceException("IP地址格式不正确");
|
}
|
}
|
catch (Exception exception)
|
{
|
throw new ServiceException("IP地址格式不正确");
|
}
|
}
|
|
/**
|
* 校验可选的二值状态查询条件。
|
*/
|
private void validateOptionalStatus(String status, String name)
|
{
|
if (StringUtils.isNotEmpty(status))
|
{
|
validateStatus(status, name);
|
}
|
}
|
|
/**
|
* 校验状态值只能为 0 或 1。
|
*/
|
private void validateStatus(String status, String name)
|
{
|
if (!ENABLED.equals(status) && !DISABLED.equals(status))
|
{
|
throw new ServiceException(name + "只能为0或1");
|
}
|
}
|
|
/**
|
* 删除或禁用前校验采集器不处于测试中。
|
*/
|
private void validateCollectorNotTesting(TCollector collector, String operation)
|
{
|
if (TESTING.equals(collector.getTestStatus()))
|
{
|
throw new ServiceException("采集器正在测试,不能" + operation);
|
}
|
}
|
|
/**
|
* 去除字符串首尾空白,并将空字符串转换为 null。
|
*/
|
private String trimToNull(String value)
|
{
|
return StringUtils.isEmpty(StringUtils.trim(value)) ? null : StringUtils.trim(value);
|
}
|
|
private Map<String, CollectorCandidate> candidatesByCode()
|
{
|
return candidateStore.snapshot(Instant.now()).collectors().stream()
|
.collect(java.util.stream.Collectors.toMap(CollectorCandidate::deviceCode, item -> item));
|
}
|
|
private TCollector applyRealtimeState(TCollector collector, Map<String, CollectorCandidate> candidates)
|
{
|
CollectorCandidate candidate = candidates.get(collector.getCollectorCode());
|
collector.setOnlineStatus(candidate != null && candidate.online() ? ONLINE : OFFLINE);
|
return collector;
|
}
|
}
|