feat(ExtraDevice): 添加外部设备管理功能

- 创建了 ExtraDeviceController 提供分页查询外部设备列表的API接口
- 新增 ExtraDeviceService 和 ExtraDeviceServiceImpl 实现设备查询逻辑
- 添加 ExtraDevicePageQueryReq 和 ExtraDeviceRespVO 请求响应数据模型
- 扩展 ExtraDeviceMapper 支持分页查询外部设备列表
- 实现景区名称填充和设备在线状态判断功能
- 集成 Redis 获取设备心跳时间判断在线状态
- 添加了完整的参数校验和异常处理机制
This commit is contained in:
2025-12-29 16:06:32 +08:00
parent d6780ccb7a
commit 4596a61ba8
7 changed files with 304 additions and 1 deletions

View File

@@ -0,0 +1,17 @@
package com.ycwl.basic.service.pc;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.extraDevice.req.ExtraDevicePageQueryReq;
import com.ycwl.basic.model.pc.extraDevice.resp.ExtraDeviceRespVO;
/**
* 外部设备服务接口
*/
public interface ExtraDeviceService {
/**
* 分页查询外部设备列表
* @param req 查询请求参数
* @return 分页结果
*/
PageInfo<ExtraDeviceRespVO> pageQuery(ExtraDevicePageQueryReq req);
}

View File

@@ -0,0 +1,140 @@
package com.ycwl.basic.service.pc.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
import com.ycwl.basic.mapper.ExtraDeviceMapper;
import com.ycwl.basic.model.pc.extraDevice.req.ExtraDevicePageQueryReq;
import com.ycwl.basic.model.pc.extraDevice.resp.ExtraDeviceRespVO;
import com.ycwl.basic.repository.ScenicRepository;
import com.ycwl.basic.service.pc.ExtraDeviceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* 外部设备服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ExtraDeviceServiceImpl implements ExtraDeviceService {
private final ExtraDeviceMapper extraDeviceMapper;
private final ScenicRepository scenicRepository;
private final RedisTemplate<String, String> redisTemplate;
/**
* Redis Key前缀:ext_device:online:{ident}
*/
private static final String REDIS_KEY_PREFIX = "ext_device:online:";
/**
* 在线判断阈值:5分钟(300秒)
*/
private static final long ONLINE_THRESHOLD_SECONDS = 300;
/**
* 时间格式:yyyy-MM-dd HH:mm:ss
*/
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
@Override
public PageInfo<ExtraDeviceRespVO> pageQuery(ExtraDevicePageQueryReq req) {
// 参数校验与默认值设置
if (req.getPageNum() == null || req.getPageNum() < 1) {
req.setPageNum(1);
}
if (req.getPageSize() == null || req.getPageSize() < 1) {
req.setPageSize(20);
}
// 使用PageHelper进行分页
PageHelper.startPage(req.getPageNum(), req.getPageSize());
// 查询数据库获取设备基础信息
List<ExtraDeviceRespVO> devices = extraDeviceMapper.pageQuery(req.getScenicId());
// 遍历设备列表,填充景区名称和在线状态
for (ExtraDeviceRespVO device : devices) {
// 1. 填充景区名称
fillScenicName(device);
// 2. 填充在线状态和心跳时间
fillOnlineStatus(device);
}
return new PageInfo<>(devices);
}
/**
* 填充景区名称
* 从景区服务获取景区信息,优先从远程服务获取,失败则降级读取缓存
*/
private void fillScenicName(ExtraDeviceRespVO device) {
try {
if (device.getScenicId() != null) {
ScenicV2DTO scenic = scenicRepository.getScenicBasic(device.getScenicId());
if (scenic != null && StringUtils.isNotBlank(scenic.getName())) {
device.setScenicName(scenic.getName());
} else {
device.setScenicName("未知景区");
}
} else {
device.setScenicName("未知景区");
}
} catch (Exception e) {
log.warn("获取景区名称失败, scenicId: {}, error: {}", device.getScenicId(), e.getMessage());
device.setScenicName("未知景区");
}
}
/**
* 填充在线状态和心跳时间
* 从Redis获取设备心跳时间戳,判断是否在线(5分钟内有心跳)
*/
private void fillOnlineStatus(ExtraDeviceRespVO device) {
try {
if (StringUtils.isBlank(device.getIdent())) {
device.setOnline(0);
device.setKeepaliveAt("");
return;
}
// 从Redis获取心跳时间戳
String redisKey = REDIS_KEY_PREFIX + device.getIdent();
String timestampStr = redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isBlank(timestampStr) || !StringUtils.isNumeric(timestampStr)) {
// Redis中没有心跳记录或格式不正确
device.setOnline(0);
device.setKeepaliveAt("");
return;
}
// 解析时间戳(秒级)
long keepaliveTimestamp = Long.parseLong(timestampStr);
long currentTimestamp = System.currentTimeMillis() / 1000;
// 判断是否在线(5分钟内有心跳)
boolean isOnline = (currentTimestamp - keepaliveTimestamp) < ONLINE_THRESHOLD_SECONDS;
device.setOnline(isOnline ? 1 : 0);
// 格式化心跳时间
Date keepaliveDate = new Date(keepaliveTimestamp * 1000);
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
device.setKeepaliveAt(sdf.format(keepaliveDate));
} catch (Exception e) {
log.warn("获取设备在线状态失败, ident: {}, error: {}", device.getIdent(), e.getMessage());
device.setOnline(0);
device.setKeepaliveAt("");
}
}
}