feat(iot,ops): 区域设备关联接口返回更多设备信息,修复 N+1 和代码质量问题
- IotDeviceSimpleRespDTO 新增 nickname、serialNumber、state、deviceType 字段 - IotDeviceQueryApi 新增 batchGetDevices 批量查询接口 - IotDeviceQueryApiImpl 提取 toSimpleDTO 统一转换、通过产品缓存解析 productName、 移除 blanket try-catch 让异常正确传播、删除无用 import - AreaDeviceRelationRespVO 新增 nickname、serialNumber、deviceState、deviceType 字段 - AreaDeviceRelationServiceImpl.listByAreaId 改为批量查询避免 N+1 RPC、 增加 null 防护;bindDevice 改为 fail-fast 不再存脏数据 - ErrorCodeConstants 新增 IOT_SERVICE_UNAVAILABLE 错误码 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,18 @@ public class AreaDeviceRelationRespVO {
|
||||
@Schema(description = "设备名称", example = "客流计数器001")
|
||||
private String deviceName;
|
||||
|
||||
@Schema(description = "设备备注名称", example = "A栋3层客流计数器")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "设备序列号", example = "SN20250101001")
|
||||
private String serialNumber;
|
||||
|
||||
@Schema(description = "设备状态(0-未激活 1-在线 2-离线)", example = "1")
|
||||
private Integer deviceState;
|
||||
|
||||
@Schema(description = "设备类型", example = "10")
|
||||
private Integer deviceType;
|
||||
|
||||
@Schema(description = "产品ID", example = "10")
|
||||
private Long productId;
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.viewsh.module.ops.enums.AreaTypeEnum.*;
|
||||
|
||||
@@ -34,17 +34,17 @@ import static com.viewsh.module.ops.enums.AreaTypeEnum.*;
|
||||
@Slf4j
|
||||
public class AreaDeviceRelationServiceImpl implements AreaDeviceRelationService {
|
||||
|
||||
@Resource
|
||||
private OpsAreaDeviceRelationMapper opsAreaDeviceRelationMapper;
|
||||
|
||||
@Resource
|
||||
private OpsBusAreaMapper opsBusAreaMapper;
|
||||
|
||||
@Resource
|
||||
private IotDeviceQueryApi iotDeviceQueryApi;
|
||||
|
||||
@Resource
|
||||
private AreaDeviceService areaDeviceService;
|
||||
@Resource
|
||||
private OpsAreaDeviceRelationMapper opsAreaDeviceRelationMapper;
|
||||
|
||||
@Resource
|
||||
private OpsBusAreaMapper opsBusAreaMapper;
|
||||
|
||||
@Resource
|
||||
private IotDeviceQueryApi iotDeviceQueryApi;
|
||||
|
||||
@Resource
|
||||
private AreaDeviceService areaDeviceService;
|
||||
|
||||
private static final String TYPE_TRAFFIC_COUNTER = "TRAFFIC_COUNTER";
|
||||
private static final String TYPE_BEACON = "BEACON";
|
||||
@@ -53,9 +53,19 @@ public class AreaDeviceRelationServiceImpl implements AreaDeviceRelationService
|
||||
@Override
|
||||
public List<AreaDeviceRelationRespVO> listByAreaId(Long areaId) {
|
||||
List<OpsAreaDeviceRelationDO> list = opsAreaDeviceRelationMapper.selectListByAreaId(areaId);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 批量查询设备信息,避免 N+1 RPC 调用
|
||||
Set<Long> deviceIds = list.stream()
|
||||
.map(OpsAreaDeviceRelationDO::getDeviceId)
|
||||
.collect(Collectors.toSet());
|
||||
Map<Long, IotDeviceSimpleRespDTO> deviceMap = batchGetDeviceMap(deviceIds);
|
||||
|
||||
return list.stream()
|
||||
.map(this::convertToRespVO)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
.map(relation -> convertToRespVO(relation, deviceMap.get(relation.getDeviceId())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,43 +97,27 @@ public class AreaDeviceRelationServiceImpl implements AreaDeviceRelationService
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 IoT 接口获取设备信息
|
||||
String deviceKey = "DEVICE_" + bindReq.getDeviceId();
|
||||
Long productId = 0L;
|
||||
String productKey = "PRODUCT_DEFAULT";
|
||||
|
||||
try {
|
||||
CommonResult<IotDeviceSimpleRespDTO> result = iotDeviceQueryApi.getDevice(bindReq.getDeviceId());
|
||||
if (result != null && result.getData() != null) {
|
||||
IotDeviceSimpleRespDTO device = result.getData();
|
||||
deviceKey = device.getDeviceName() != null ? device.getDeviceName() : deviceKey;
|
||||
productId = device.getProductId() != null ? device.getProductId() : 0L;
|
||||
productKey = device.getProductKey() != null ? device.getProductKey() : productKey;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[bindDevice] 调用 IoT 接口获取设备信息失败: deviceId={}, error={}",
|
||||
bindReq.getDeviceId(), e.getMessage());
|
||||
// 降级:使用默认值
|
||||
}
|
||||
// 调用 IoT 接口获取设备信息(失败则阻断绑定)
|
||||
IotDeviceSimpleRespDTO device = getDeviceOrThrow(bindReq.getDeviceId());
|
||||
|
||||
OpsAreaDeviceRelationDO relation = OpsAreaDeviceRelationDO.builder()
|
||||
.areaId(bindReq.getAreaId())
|
||||
.deviceId(bindReq.getDeviceId())
|
||||
.deviceKey(deviceKey)
|
||||
.productId(productId)
|
||||
.productKey(productKey)
|
||||
.deviceKey(device.getDeviceName())
|
||||
.productId(device.getProductId())
|
||||
.productKey(device.getProductKey())
|
||||
.relationType(bindReq.getRelationType())
|
||||
.configData(bindReq.getConfigData() != null ? bindReq.getConfigData() : new HashMap<>())
|
||||
.enabled(true)
|
||||
.build();
|
||||
|
||||
opsAreaDeviceRelationMapper.insert(relation);
|
||||
|
||||
// 清除可能存在的 NULL_CACHE 标记
|
||||
areaDeviceService.evictConfigCache(relation.getAreaId(), relation.getRelationType());
|
||||
|
||||
return relation.getId();
|
||||
}
|
||||
opsAreaDeviceRelationMapper.insert(relation);
|
||||
|
||||
// 清除可能存在的 NULL_CACHE 标记
|
||||
areaDeviceService.evictConfigCache(relation.getAreaId(), relation.getRelationType());
|
||||
|
||||
return relation.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -141,60 +135,85 @@ public class AreaDeviceRelationServiceImpl implements AreaDeviceRelationService
|
||||
relation.setConfigData(updateReq.getConfigData());
|
||||
}
|
||||
|
||||
// enabled 更新
|
||||
if (updateReq.getEnabled() != null) {
|
||||
relation.setEnabled(updateReq.getEnabled());
|
||||
}
|
||||
|
||||
opsAreaDeviceRelationMapper.updateById(relation);
|
||||
|
||||
// 删缓存以同步 Redis
|
||||
areaDeviceService.evictConfigCache(existing.getAreaId(), existing.getRelationType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean unbindDevice(Long id) {
|
||||
OpsAreaDeviceRelationDO existing = opsAreaDeviceRelationMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean deleted = opsAreaDeviceRelationMapper.deleteById(id) > 0;
|
||||
if (deleted) {
|
||||
// 同步 Redis 缓存
|
||||
areaDeviceService.evictConfigCache(existing.getAreaId(), existing.getRelationType());
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为响应 VO
|
||||
* 从 IoT 模块获取设备名称和产品名称(带降级)
|
||||
*/
|
||||
private AreaDeviceRelationRespVO convertToRespVO(OpsAreaDeviceRelationDO relation) {
|
||||
AreaDeviceRelationRespVO respVO = BeanUtil.copyProperties(relation, AreaDeviceRelationRespVO.class);
|
||||
|
||||
// 从 IoT 模块获取设备信息
|
||||
try {
|
||||
CommonResult<IotDeviceSimpleRespDTO> result = iotDeviceQueryApi.getDevice(relation.getDeviceId());
|
||||
if (result != null && result.getData() != null) {
|
||||
IotDeviceSimpleRespDTO device = result.getData();
|
||||
respVO.setDeviceName(device.getDeviceName());
|
||||
respVO.setProductName(device.getProductName());
|
||||
} else {
|
||||
// 降级:使用默认值
|
||||
respVO.setDeviceName("设备_" + relation.getDeviceId());
|
||||
respVO.setProductName("产品_" + relation.getProductKey());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[convertToRespVO] 调用 IoT 接口获取设备信息失败: deviceId={}, error={}",
|
||||
relation.getDeviceId(), e.getMessage());
|
||||
// 降级:使用默认值
|
||||
respVO.setDeviceName("设备_" + relation.getDeviceId());
|
||||
respVO.setProductName("产品_" + relation.getProductKey());
|
||||
// enabled 更新
|
||||
if (updateReq.getEnabled() != null) {
|
||||
relation.setEnabled(updateReq.getEnabled());
|
||||
}
|
||||
|
||||
opsAreaDeviceRelationMapper.updateById(relation);
|
||||
|
||||
// 删缓存以同步 Redis
|
||||
areaDeviceService.evictConfigCache(existing.getAreaId(), existing.getRelationType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean unbindDevice(Long id) {
|
||||
OpsAreaDeviceRelationDO existing = opsAreaDeviceRelationMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean deleted = opsAreaDeviceRelationMapper.deleteById(id) > 0;
|
||||
if (deleted) {
|
||||
// 同步 Redis 缓存
|
||||
areaDeviceService.evictConfigCache(existing.getAreaId(), existing.getRelationType());
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询设备信息,返回 deviceId → DTO 映射
|
||||
*/
|
||||
private Map<Long, IotDeviceSimpleRespDTO> batchGetDeviceMap(Set<Long> deviceIds) {
|
||||
if (deviceIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
try {
|
||||
CommonResult<List<IotDeviceSimpleRespDTO>> result = iotDeviceQueryApi.batchGetDevices(deviceIds);
|
||||
if (result != null && result.getData() != null) {
|
||||
return result.getData().stream()
|
||||
.collect(Collectors.toMap(IotDeviceSimpleRespDTO::getId, Function.identity()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[batchGetDeviceMap] 批量查询设备信息失败: deviceIds={}, error={}", deviceIds, e.getMessage());
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个设备信息,失败则抛出异常阻断操作
|
||||
*/
|
||||
private IotDeviceSimpleRespDTO getDeviceOrThrow(Long deviceId) {
|
||||
try {
|
||||
CommonResult<IotDeviceSimpleRespDTO> result = iotDeviceQueryApi.getDevice(deviceId);
|
||||
if (result != null && result.getData() != null) {
|
||||
return result.getData();
|
||||
}
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEVICE_NOT_FOUND);
|
||||
} catch (com.viewsh.framework.common.exception.ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("[getDeviceOrThrow] 调用 IoT 接口获取设备信息失败: deviceId={}, error={}",
|
||||
deviceId, e.getMessage());
|
||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.IOT_SERVICE_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为响应 VO(使用预查询的设备信息,避免 N+1)
|
||||
*/
|
||||
private AreaDeviceRelationRespVO convertToRespVO(OpsAreaDeviceRelationDO relation,
|
||||
IotDeviceSimpleRespDTO device) {
|
||||
AreaDeviceRelationRespVO respVO = BeanUtil.copyProperties(relation, AreaDeviceRelationRespVO.class);
|
||||
if (device != null) {
|
||||
respVO.setDeviceName(device.getDeviceName());
|
||||
respVO.setProductName(device.getProductName());
|
||||
respVO.setNickname(device.getNickname());
|
||||
respVO.setSerialNumber(device.getSerialNumber());
|
||||
respVO.setDeviceState(device.getState());
|
||||
respVO.setDeviceType(device.getDeviceType());
|
||||
}
|
||||
return respVO;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user