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;
|
package com.ycwl.basic.mapper;
|
||||||
|
|
||||||
import com.ycwl.basic.model.pc.device.resp.DeviceRespVO;
|
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.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ExtraDeviceMapper {
|
public interface ExtraDeviceMapper {
|
||||||
List<DeviceRespVO> listExtraDeviceByScenicId(Long scenicId);
|
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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,24 @@
|
|||||||
<select id="listExtraDeviceByScenicId" resultType="com.ycwl.basic.model.pc.device.resp.DeviceRespVO">
|
<select id="listExtraDeviceByScenicId" resultType="com.ycwl.basic.model.pc.device.resp.DeviceRespVO">
|
||||||
select d.id, d.ident as no, d.scenic_id, d.name, d.status
|
select d.id, d.ident as no, d.scenic_id, d.name, d.status
|
||||||
from extra_device d
|
from extra_device d
|
||||||
|
|
||||||
where d.scenic_id = #{scenicId}
|
where d.scenic_id = #{scenicId}
|
||||||
and d.status = 1
|
and d.status = 1
|
||||||
</select>
|
</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>
|
</mapper>
|
||||||
Reference in New Issue
Block a user