You've already forked FrameTour-BE
feat(ExtraDevice): 添加外部设备管理功能
- 创建了 ExtraDeviceController 提供分页查询外部设备列表的API接口 - 新增 ExtraDeviceService 和 ExtraDeviceServiceImpl 实现设备查询逻辑 - 添加 ExtraDevicePageQueryReq 和 ExtraDeviceRespVO 请求响应数据模型 - 扩展 ExtraDeviceMapper 支持分页查询外部设备列表 - 实现景区名称填充和设备在线状态判断功能 - 集成 Redis 获取设备心跳时间判断在线状态 - 添加了完整的参数校验和异常处理机制
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package com.ycwl.basic.controller.pc;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.ycwl.basic.model.pc.extraDevice.req.ExtraDevicePageQueryReq;
|
||||
import com.ycwl.basic.model.pc.extraDevice.resp.ExtraDeviceRespVO;
|
||||
import com.ycwl.basic.service.pc.ExtraDeviceService;
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 外部设备管理控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/extra_device/v1")
|
||||
@RequiredArgsConstructor
|
||||
public class ExtraDeviceController {
|
||||
|
||||
private final ExtraDeviceService extraDeviceService;
|
||||
|
||||
/**
|
||||
* 分页查询外部设备列表
|
||||
*
|
||||
* @param req 查询请求参数,包含scenicId(可选)、pageNum、pageSize
|
||||
* @return 分页查询结果,包含设备ID、景区ID、景区名称、设备名称、标识、状态、心跳时间、在线状态
|
||||
*/
|
||||
@PostMapping("/page")
|
||||
public ApiResponse<PageInfo<ExtraDeviceRespVO>> page(@RequestBody ExtraDevicePageQueryReq req) {
|
||||
log.info("分页查询外部设备列表, scenicId: {}, pageNum: {}, pageSize: {}",
|
||||
req.getScenicId(), req.getPageNum(), req.getPageSize());
|
||||
|
||||
PageInfo<ExtraDeviceRespVO> pageInfo = extraDeviceService.pageQuery(req);
|
||||
|
||||
log.info("外部设备列表查询完成, total: {}, pages: {}",
|
||||
pageInfo.getTotal(), pageInfo.getPages());
|
||||
|
||||
return ApiResponse.success(pageInfo);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,20 @@
|
||||
package com.ycwl.basic.mapper;
|
||||
|
||||
import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
|
||||
import com.ycwl.basic.model.pc.extraDevice.resp.ExtraDeviceRespVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ExtraDeviceMapper {
|
||||
List<DeviceRespVO> listExtraDeviceByScenicId(Long scenicId);
|
||||
|
||||
/**
|
||||
* 分页查询外部设备列表
|
||||
* @param scenicId 景区ID (可选)
|
||||
* @return 外部设备列表
|
||||
*/
|
||||
List<ExtraDeviceRespVO> pageQuery(@Param("scenicId") Long scenicId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ycwl.basic.model.pc.extraDevice.req;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部设备分页查询请求
|
||||
*/
|
||||
@Data
|
||||
public class ExtraDevicePageQueryReq {
|
||||
/**
|
||||
* 景区ID (支持 Long 或字符串格式的Long)
|
||||
*/
|
||||
private Long scenicId;
|
||||
|
||||
/**
|
||||
* 页码,默认1
|
||||
*/
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页大小,默认20
|
||||
*/
|
||||
private Integer pageSize = 20;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ycwl.basic.model.pc.extraDevice.resp;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 外部设备响应VO
|
||||
* 对应文档中的 ExtraDeviceResp 结构
|
||||
*/
|
||||
@Data
|
||||
public class ExtraDeviceRespVO {
|
||||
/**
|
||||
* 设备ID (JSON输出为字符串)
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 景区ID (JSON输出为字符串)
|
||||
*/
|
||||
private Long scenicId;
|
||||
|
||||
/**
|
||||
* 景区名称 (从景区服务获取)
|
||||
*/
|
||||
private String scenicName;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 设备标识
|
||||
*/
|
||||
private String ident;
|
||||
|
||||
/**
|
||||
* 数据库状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 心跳时间 (格式: yyyy-MM-dd HH:mm:ss)
|
||||
*/
|
||||
private String keepaliveAt;
|
||||
|
||||
/**
|
||||
* 在线状态: 1=在线, 0=离线
|
||||
* 判断逻辑:5分钟内有心跳则在线
|
||||
*/
|
||||
private Integer online;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,20 @@
|
||||
where d.scenic_id = #{scenicId}
|
||||
and d.status = 1
|
||||
</select>
|
||||
|
||||
<select id="pageQuery" resultType="com.ycwl.basic.model.pc.extraDevice.resp.ExtraDeviceRespVO">
|
||||
select
|
||||
d.id,
|
||||
d.scenic_id as scenicId,
|
||||
d.name,
|
||||
d.ident,
|
||||
d.status
|
||||
from extra_device d
|
||||
<where>
|
||||
<if test="scenicId != null">
|
||||
and d.scenic_id = #{scenicId}
|
||||
</if>
|
||||
</where>
|
||||
order by d.id desc
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user