feat(iot): add-iot-clean-order-integration阶段一-基础设施
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
package com.viewsh.module.iot.dal.dataobject.integration.clean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 蓝牙信标检测配置
|
||||
* <p>
|
||||
* 用于配置基于工牌蓝牙信标检测自动确认到岗/离岗的规则
|
||||
* 采用"强进弱出"双阈值算法,避免信号抖动导致的误判
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Data
|
||||
public class BeaconPresenceConfig {
|
||||
|
||||
/**
|
||||
* 是否启用信标检测
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 目标信标 MAC 地址
|
||||
* <p>
|
||||
* 例如:F0:C8:60:1D:10:BB
|
||||
*/
|
||||
private String beaconMac;
|
||||
|
||||
/**
|
||||
* <20><>动窗口配置
|
||||
*/
|
||||
private WindowConfig window;
|
||||
|
||||
/**
|
||||
* 进入(到岗)判定配置 - 强阈值
|
||||
*/
|
||||
private EnterConfig enter;
|
||||
|
||||
/**
|
||||
* 退出(离岗)判定配置 - 弱阈值
|
||||
*/
|
||||
private ExitConfig exit;
|
||||
|
||||
/**
|
||||
* 滑动窗口配置
|
||||
*/
|
||||
@Data
|
||||
public static class WindowConfig {
|
||||
|
||||
/**
|
||||
* 样本在 Redis 中的保留时间(秒)
|
||||
* <p>
|
||||
* 超过此时间的样本将被自动清理,防止 Redis 内存溢出
|
||||
*/
|
||||
private Integer sampleTtlSeconds;
|
||||
|
||||
/**
|
||||
* 未检测到信标时的默认填充值
|
||||
* <p>
|
||||
* 当工牌未检测到目标信标时,使用此值填充窗口
|
||||
* 建议设置为 -999(明显低于正常 RSSI 值)
|
||||
*/
|
||||
private Integer missingValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入(到岗)判定配置 - 强阈值
|
||||
* <p>
|
||||
* 使用强阈值(如 -70dBm)可以避免路过误判,只有信号足够强才算到达
|
||||
*/
|
||||
@Data
|
||||
public static class EnterConfig {
|
||||
|
||||
/**
|
||||
* RSSI 强阈值(dBm)
|
||||
* <p>
|
||||
* 只有 RSSI >= 此值才算有效信号
|
||||
* 例如:-70
|
||||
*/
|
||||
private Integer rssiThreshold;
|
||||
|
||||
/**
|
||||
* 滑动窗口大小
|
||||
* <p>
|
||||
* 采样窗口中的样本数量
|
||||
* 例如:3 表示取最近 3 次采样
|
||||
*/
|
||||
private Integer windowSize;
|
||||
|
||||
/**
|
||||
* 命中次数
|
||||
* <p>
|
||||
* 窗口中满足阈值的次数达到此值时,判定为到达
|
||||
* 例如:2 表示 3 次中有 2 次满足即判定到达
|
||||
*/
|
||||
private Integer hitCount;
|
||||
|
||||
/**
|
||||
* 是否自动触发到岗事件
|
||||
* <p>
|
||||
* true = 检测到到达后自动发布 ops.order.arrive 事件
|
||||
* false = 仅记录日志,不触发事件
|
||||
*/
|
||||
private Boolean autoArrival;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出(离岗)判定配置 - 弱阈值
|
||||
* <p>
|
||||
* 使用弱阈值(如 -85dBm)和迟滞设计,避免边缘抖动
|
||||
* 只有信号足够弱或彻底消失才算离开
|
||||
*/
|
||||
@Data
|
||||
public static class ExitConfig {
|
||||
|
||||
/**
|
||||
* RSSI 弱阈值(dBm)
|
||||
* <p>
|
||||
* RSSI < 此值或信号丢失才算离开
|
||||
* 例如:-85
|
||||
*/
|
||||
private Integer weakRssiThreshold;
|
||||
|
||||
/**
|
||||
* 滑动窗口大小
|
||||
*/
|
||||
private Integer windowSize;
|
||||
|
||||
/**
|
||||
* 命中次数
|
||||
* <p>
|
||||
* 窗口中满足弱阈值(丢失)的次数达到此值时,判定为离开
|
||||
*/
|
||||
private Integer hitCount;
|
||||
|
||||
/**
|
||||
* 确认离开后多久发送警告(分钟)
|
||||
* <p>
|
||||
* 0 = 立即发送
|
||||
*/
|
||||
private Integer warningDelayMinutes;
|
||||
|
||||
/**
|
||||
* 持续丢失多久后触发自动完成(分钟)
|
||||
* <p>
|
||||
* 从首次丢失开始计时,持续丢失超过此时间后自动完成工单
|
||||
*/
|
||||
private Integer lossTimeoutMinutes;
|
||||
|
||||
/**
|
||||
* 最小有效作业时长(分钟)
|
||||
* <p>
|
||||
* 用于防止"打卡即走"作弊
|
||||
* 如果作业时长(从到岗到离开)小于此值,则抑制自动完成
|
||||
*/
|
||||
private Integer minValidWorkMinutes;
|
||||
|
||||
/**
|
||||
* 是否自动触发完成事件
|
||||
* <p>
|
||||
* true = 检测到离开超时后自动发布 ops.order.complete 事件
|
||||
* false = 仅记录日志,不触发事件
|
||||
*/
|
||||
private Boolean autoComplete;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.viewsh.module.iot.dal.dataobject.integration.clean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 按键事件配置
|
||||
* <p>
|
||||
* 用于配置工牌按键事件的处理规则
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Data
|
||||
public class ButtonEventConfig {
|
||||
|
||||
/**
|
||||
* 是否启用按键事件处理
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 确认键 ID
|
||||
* <p>
|
||||
* 当保洁员按下此按键时,触发工单确认事件
|
||||
*/
|
||||
private Integer confirmKeyId;
|
||||
|
||||
/**
|
||||
* 查询键 ID
|
||||
* <p>
|
||||
* 当保洁员按下此按键时,触发工单查询事件
|
||||
*/
|
||||
private Integer queryKeyId;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.viewsh.module.iot.dal.dataobject.integration.clean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 保洁工单集成配置
|
||||
* <p>
|
||||
* 这是 IoT 设备与保洁工单集成的总配置类,包含所有子配置
|
||||
* 存储在 {@link OpsAreaDeviceRelationDO#getConfigData()} 中
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Data
|
||||
public class CleanOrderIntegrationConfig {
|
||||
|
||||
/**
|
||||
* 客流阈值配置
|
||||
* <p>
|
||||
* 用于基于客流计数器自动触发工单创建
|
||||
*/
|
||||
private TrafficThresholdConfig trafficThreshold;
|
||||
|
||||
/**
|
||||
* 蓝牙信标检测配置
|
||||
* <p>
|
||||
* 用于基于工牌蓝牙信标检测自动确认到岗/离岗
|
||||
*/
|
||||
private BeaconPresenceConfig beaconPresence;
|
||||
|
||||
/**
|
||||
* 按键事件配置
|
||||
* <p>
|
||||
* 用于工牌按键事件的处理
|
||||
*/
|
||||
private ButtonEventConfig buttonEvent;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.viewsh.module.iot.dal.dataobject.integration.clean;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.viewsh.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 运营区域设备关联 DO
|
||||
* <p>
|
||||
* 用于建立运营区域与 IoT 设备的绑定关系,并存储核心检测配置
|
||||
* 注意:此表在 ops 库中创建,但在 IoT 模块中<E59D97><E4B8AD>问(通过 Feign 或直接访问)
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@TableName(value = "ops_area_device_relation", autoResultMap = true)
|
||||
@KeySequence("ops_area_device_relation_seq")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OpsAreaDeviceRelationDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 运营区域ID
|
||||
* <p>
|
||||
* 关联 ops_bus_area.id
|
||||
*/
|
||||
private Long areaId;
|
||||
|
||||
/**
|
||||
* IoT设备ID
|
||||
* <p>
|
||||
* 关联 iot_device.id
|
||||
*/
|
||||
private Long deviceId;
|
||||
|
||||
/**
|
||||
* 设备Key(冗余字段,便于查询)
|
||||
*/
|
||||
private String deviceKey;
|
||||
|
||||
/**
|
||||
* 产品ID
|
||||
* <p>
|
||||
* 关联 iot_product.id
|
||||
*/
|
||||
private Long productId;
|
||||
|
||||
/**
|
||||
* 产品Key(冗余字段,便于查询)
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 关联类型
|
||||
* <p>
|
||||
* TRAFFIC_COUNTER - 客流计数器
|
||||
* BEACON - 蓝牙信标
|
||||
* BADGE - 工牌设备
|
||||
*/
|
||||
private String relationType;
|
||||
|
||||
/**
|
||||
* 配置数据(JSON格式)
|
||||
* <p>
|
||||
* 存储保洁工单集成的各类配置
|
||||
* 使用 {@link JacksonTypeHandler} 自动序列化/反序列化
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private CleanOrderIntegrationConfig configData;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
* <p>
|
||||
* true - 启用
|
||||
* false - 禁用
|
||||
*/
|
||||
private Boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.viewsh.module.iot.dal.dataobject.integration.clean;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 客流阈值配置
|
||||
* <p>
|
||||
* 用于配置基于客流计数器自动触发工单创建的规则
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Data
|
||||
public class TrafficThresholdConfig {
|
||||
|
||||
/**
|
||||
* 触发阈值
|
||||
* <p>
|
||||
* 当实际客流(当前值 - 基准值)达到此阈值时,触发工单创建
|
||||
*/
|
||||
private Integer threshold;
|
||||
|
||||
/**
|
||||
* 统计时间窗口(秒)
|
||||
* <p>
|
||||
* 在此时间窗口内,同一设备只能触发一次工单创建,防止重复
|
||||
*/
|
||||
private Integer timeWindowSeconds;
|
||||
|
||||
/**
|
||||
* 是否自动创建工单
|
||||
* <p>
|
||||
* true = 自动创建工单
|
||||
* false = 仅记录日志,不创建工单
|
||||
*/
|
||||
private Boolean autoCreateOrder;
|
||||
|
||||
/**
|
||||
* 工单优先级
|
||||
* <p>
|
||||
* P0 = 紧急
|
||||
* P1 = 重要
|
||||
* P2 = 普通
|
||||
*/
|
||||
private String orderPriority;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.viewsh.module.iot.dal.mysql.integration.clean;
|
||||
|
||||
import com.viewsh.framework.common.pojo.PageResult;
|
||||
import com.viewsh.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.viewsh.module.iot.dal.dataobject.integration.clean.OpsAreaDeviceRelationDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 运营区域设备关联 Mapper
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Mapper
|
||||
public interface OpsAreaDeviceRelationMapper extends BaseMapperX<OpsAreaDeviceRelationDO> {
|
||||
|
||||
/**
|
||||
* 根据设备ID查询关联关系(仅查询启用的)
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 关联关系
|
||||
*/
|
||||
default OpsAreaDeviceRelationDO selectByDeviceId(Long deviceId) {
|
||||
return selectOne(new LambdaQueryWrapperX<OpsAreaDeviceRelationDO>()
|
||||
.eq(OpsAreaDeviceRelationDO::getDeviceId, deviceId)
|
||||
.eq(OpsAreaDeviceRelationDO::getEnabled, true)
|
||||
.orderByDesc(OpsAreaDeviceRelationDO::getCreateTime)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据区域ID查询所有启用的关联关系
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 关联关系列表
|
||||
*/
|
||||
default List<OpsAreaDeviceRelationDO> selectListByAreaId(Long areaId) {
|
||||
return selectList(new LambdaQueryWrapperX<OpsAreaDeviceRelationDO>()
|
||||
.eq(OpsAreaDeviceRelationDO::getAreaId, areaId)
|
||||
.eq(OpsAreaDeviceRelationDO::getEnabled, true)
|
||||
.orderByDesc(OpsAreaDeviceRelationDO::getCreateTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据区域ID和关联类型查询关联关系
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @param relationType 关联类型(TRAFFIC_COUNTER/BEACON/BADGE)
|
||||
* @return 关联关系列表
|
||||
*/
|
||||
default List<OpsAreaDeviceRelationDO> selectListByAreaIdAndRelationType(Long areaId, String relationType) {
|
||||
return selectList(new LambdaQueryWrapperX<OpsAreaDeviceRelationDO>()
|
||||
.eq(OpsAreaDeviceRelationDO::getAreaId, areaId)
|
||||
.eq(OpsAreaDeviceRelationDO::getRelationType, relationType)
|
||||
.eq(OpsAreaDeviceRelationDO::getEnabled, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据产品Key查询所有关联关系
|
||||
*
|
||||
* @param productKey 产品Key
|
||||
* @return 关联关系列表
|
||||
*/
|
||||
default List<OpsAreaDeviceRelationDO> selectListByProductKey(String productKey) {
|
||||
return selectList(new LambdaQueryWrapperX<OpsAreaDeviceRelationDO>()
|
||||
.eq(OpsAreaDeviceRelationDO::getProductKey, productKey)
|
||||
.eq(OpsAreaDeviceRelationDO::getEnabled, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.viewsh.module.iot.dal.redis.clean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* 蓝牙信标到达时间 Redis DAO
|
||||
* <p>
|
||||
* 用于记录保洁员首次进入区域的时间戳
|
||||
* 用于计算作业时长(Duration = 离开时间 - 到达时间)
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Repository
|
||||
public class BeaconArrivedTimeRedisDAO {
|
||||
|
||||
/**
|
||||
* 到达时间 Key 模式
|
||||
* <p>
|
||||
* 格式:iot:clean:beacon:arrivedAt:{deviceId}:{areaId}
|
||||
*/
|
||||
private static final String ARRIVED_AT_KEY_PATTERN = "iot:clean:beacon:arrivedAt:%s:%s";
|
||||
|
||||
/**
|
||||
* 到达时间的 TTL(秒)
|
||||
* <p>
|
||||
* 默认保留 24 小时
|
||||
*/
|
||||
private static final int ARRIVED_AT_TTL_SECONDS = 86400;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 记录到达时间
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
*/
|
||||
public void recordArrivedTime(Long deviceId, Long areaId, Long timestamp) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
stringRedisTemplate.opsForValue().set(key, String.valueOf(timestamp),
|
||||
ARRIVED_AT_TTL_SECONDS, java.util.concurrent.TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到达时间
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @return 时间戳(毫秒),如果不存在返回 null
|
||||
*/
|
||||
public Long getArrivedTime(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
String timestampStr = stringRedisTemplate.opsForValue().get(key);
|
||||
|
||||
if (timestampStr == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Long.parseLong(timestampStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除到达时间
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
*/
|
||||
public void clearArrivedTime(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Redis Key
|
||||
*/
|
||||
private static String formatKey(Long deviceId, Long areaId) {
|
||||
return String.format(ARRIVED_AT_KEY_PATTERN, deviceId, areaId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.viewsh.module.iot.dal.redis.clean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 蓝牙信标 RSSI 滑动窗口 Redis DAO
|
||||
* <p>
|
||||
* 用于存储工牌检测到的信标 RSSI 值的滑动窗口样本
|
||||
* 支持抗抖动检测(基于滑动窗口的"强进弱出"算法)
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Repository
|
||||
public class BeaconRssiWindowRedisDAO {
|
||||
|
||||
/**
|
||||
* 滑动窗口 Key 模式
|
||||
* <p>
|
||||
* 格式:iot:clean:beacon:rssi:window:{deviceId}:{areaId}
|
||||
*/
|
||||
private static final String WINDOW_KEY_PATTERN = "iot:clean:beacon:rssi:window:%s:%s";
|
||||
|
||||
/**
|
||||
* 窗口样本的 TTL(秒)
|
||||
* <p>
|
||||
* 默认保留 1 小时,防止 Redis 内存溢出
|
||||
*/
|
||||
private static final int WINDOW_TTL_SECONDS = 3600;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 更新滑动窗口(保留最近 N 个样本)
|
||||
* <p>
|
||||
* 如果窗口大小超过 maxSize,则移除最旧的样本
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @param rssi RSSI 值
|
||||
* @param maxSize 窗口最大大小
|
||||
*/
|
||||
public void updateWindow(Long deviceId, Long areaId, Integer rssi, Integer maxSize) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
|
||||
// 获取当前窗口
|
||||
List<Integer> window = getWindow(deviceId, areaId);
|
||||
|
||||
// 添加新样本
|
||||
window.add(rssi);
|
||||
|
||||
// 如果超过最大大小,移除最旧的样本
|
||||
if (window.size() > maxSize) {
|
||||
window = window.subList(window.size() - maxSize, window.size());
|
||||
}
|
||||
|
||||
// 转换为逗号分隔的字符串存储
|
||||
String windowStr = window.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
// 存储到 Redis
|
||||
stringRedisTemplate.opsForValue().set(key, windowStr, WINDOW_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取窗口样本
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @return 窗口样本列表(按时间顺序,从旧到新)
|
||||
*/
|
||||
public List<Integer> getWindow(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
String windowStr = stringRedisTemplate.opsForValue().get(key);
|
||||
|
||||
if (windowStr == null || windowStr.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 解析逗号分隔的字符串
|
||||
return List.of(windowStr.split(","))
|
||||
.stream()
|
||||
.map(Integer::parseInt)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除窗口
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
*/
|
||||
public void clearWindow(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Redis Key
|
||||
*/
|
||||
private static String formatKey(Long deviceId, Long areaId) {
|
||||
return String.format(WINDOW_KEY_PATTERN, deviceId, areaId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.viewsh.module.iot.dal.redis.clean;
|
||||
|
||||
import com.viewsh.framework.common.util.json.JsonUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 设备当前工单缓存 Redis DAO
|
||||
* <p>
|
||||
* 用于缓存设备当前执行的工单信息(由 Ops 下发)
|
||||
* 减少物联网模块查询数据库的频率
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Repository
|
||||
public class DeviceCurrentOrderRedisDAO {
|
||||
|
||||
/**
|
||||
* 工单缓存 Key 模式
|
||||
* <p>
|
||||
* 格式:ops:clean:device:order:{deviceId}
|
||||
*/
|
||||
private static final String ORDER_KEY_PATTERN = "ops:clean:device:order:%s";
|
||||
|
||||
/**
|
||||
* 工单缓存的 TTL(秒)
|
||||
* <p>
|
||||
* 默认保留 1 小时
|
||||
*/
|
||||
private static final int ORDER_TTL_SECONDS = 3600;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 缓存设备当前工单
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param orderInfo 工单缓存信息
|
||||
*/
|
||||
public void cacheCurrentOrder(Long deviceId, OrderCacheInfo orderInfo) {
|
||||
String key = formatKey(deviceId);
|
||||
String json = JsonUtils.toJsonString(orderInfo);
|
||||
stringRedisTemplate.opsForValue().set(key, json, ORDER_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前工单
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 工单缓存信息,如果不存在返回 null
|
||||
*/
|
||||
public OrderCacheInfo getCurrentOrder(Long deviceId) {
|
||||
String key = formatKey(deviceId);
|
||||
String json = stringRedisTemplate.opsForValue().get(key);
|
||||
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonUtils.parseObject(json, OrderCacheInfo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前工单缓存
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
public void clearCurrentOrder(Long deviceId) {
|
||||
String key = formatKey(deviceId);
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存被暂停的工单ID
|
||||
* <p>
|
||||
* 用于 P0 插队场景,恢复被暂停的工单
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param pausedOrderId 被暂停的工单ID
|
||||
*/
|
||||
public void cachePausedOrder(Long deviceId, Long pausedOrderId) {
|
||||
String key = formatKey(deviceId) + ":paused";
|
||||
stringRedisTemplate.opsForValue().set(key, String.valueOf(pausedOrderId),
|
||||
ORDER_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取被暂停的工单ID
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 被暂停的工单ID,如果不存在返回 null
|
||||
*/
|
||||
public Long getPausedOrderId(Long deviceId) {
|
||||
String key = formatKey(deviceId) + ":paused";
|
||||
String orderIdStr = stringRedisTemplate.opsForValue().get(key);
|
||||
return orderIdStr != null ? Long.parseLong(orderIdStr) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除被暂停的工单缓存
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
public void clearPausedOrder(Long deviceId) {
|
||||
String key = formatKey(deviceId) + ":paused";
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Redis Key
|
||||
*/
|
||||
private static String formatKey(Long deviceId) {
|
||||
return String.format(ORDER_KEY_PATTERN, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工单缓存信息
|
||||
*/
|
||||
public static class OrderCacheInfo {
|
||||
/**
|
||||
* 工单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 工单状态
|
||||
* <p>
|
||||
* DISPATCHED/ARRIVED/PAUSED/COMPLETED
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 区域ID
|
||||
*/
|
||||
private Long areaId;
|
||||
|
||||
/**
|
||||
* 信标 MAC 地址
|
||||
*/
|
||||
private String beaconMac;
|
||||
|
||||
public Long getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public void setOrderId(Long orderId) {
|
||||
this.orderId = orderId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getAreaId() {
|
||||
return areaId;
|
||||
}
|
||||
|
||||
public void setAreaId(Long areaId) {
|
||||
this.areaId = areaId;
|
||||
}
|
||||
|
||||
public String getBeaconMac() {
|
||||
return beaconMac;
|
||||
}
|
||||
|
||||
public void setBeaconMac(String beaconMac) {
|
||||
this.beaconMac = beaconMac;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.viewsh.module.iot.dal.redis.clean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 信号丢失状态记录 Redis DAO
|
||||
* <p>
|
||||
* 用于记录保洁员离岗状态,支持离岗警告防抖、超时判断及无效作业拦截
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Repository
|
||||
public class SignalLossRedisDAO {
|
||||
|
||||
/**
|
||||
* 信号丢失 Key 模式
|
||||
* <p>
|
||||
* 格式:iot:clean:signal:loss:{deviceId}:{areaId}
|
||||
* 使用 Hash 存储多个字段
|
||||
*/
|
||||
private static final String LOSS_KEY_PATTERN = "iot:clean:signal:loss:%s:%s";
|
||||
|
||||
/**
|
||||
* Hash 字段名
|
||||
*/
|
||||
private static final String FIELD_FIRST_LOSS_TIME = "firstLossTime";
|
||||
private static final String FIELD_LAST_LOSS_TIME = "lastLossTime";
|
||||
private static final String FIELD_WARNING_SENT = "warningSent";
|
||||
private static final String FIELD_INVALID_WORK_NOTIFIED = "invalidWorkNotified";
|
||||
|
||||
/**
|
||||
* 丢失记录的 TTL(秒)
|
||||
* <p>
|
||||
* 默认保留 1 小时
|
||||
*/
|
||||
private static final int LOSS_TTL_SECONDS = 3600;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 记录首次丢失
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
*/
|
||||
public void recordFirstLoss(Long deviceId, Long areaId, Long timestamp) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
|
||||
// 使用 HSETNX,仅当字段不存在时才设置(防止覆盖首次丢失时间)
|
||||
stringRedisTemplate.opsForHash().putIfAbsent(key, FIELD_FIRST_LOSS_TIME, String.valueOf(timestamp));
|
||||
|
||||
// 更新最后丢失时间
|
||||
stringRedisTemplate.opsForHash().put(key, FIELD_LAST_LOSS_TIME, String.valueOf(timestamp));
|
||||
|
||||
// 设置 TTL
|
||||
stringRedisTemplate.expire(key, LOSS_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取首次丢失时间
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @return 时间戳(毫秒),如果不存在返回 null
|
||||
*/
|
||||
public Long getFirstLossTime(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
Object value = stringRedisTemplate.opsForHash().get(key, FIELD_FIRST_LOSS_TIME);
|
||||
return value != null ? Long.parseLong(value.toString()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新最后丢失时间
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
*/
|
||||
public void updateLastLossTime(Long deviceId, Long areaId, Long timestamp) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
stringRedisTemplate.opsForHash().put(key, FIELD_LAST_LOSS_TIME, String.valueOf(timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后丢失时间
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @return 时间戳(毫秒),如果不存在返回 null
|
||||
*/
|
||||
public Long getLastLossTime(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
Object value = stringRedisTemplate.opsForHash().get(key, FIELD_LAST_LOSS_TIME);
|
||||
return value != null ? Long.parseLong(value.toString()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已发送警告
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @return true - 已发送,false - 未发送
|
||||
*/
|
||||
public Boolean isWarningSent(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
Object value = stringRedisTemplate.opsForHash().get(key, FIELD_WARNING_SENT);
|
||||
return value != null && Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记警告已发送
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
*/
|
||||
public void markWarningSent(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
stringRedisTemplate.opsForHash().put(key, FIELD_WARNING_SENT, "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已通知无效作业
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @return true - 已通知,false - 未通知
|
||||
*/
|
||||
public Boolean isInvalidWorkNotified(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
Object value = stringRedisTemplate.opsForHash().get(key, FIELD_INVALID_WORK_NOTIFIED);
|
||||
return value != null && Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记无效作业已通知
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
*/
|
||||
public void markInvalidWorkNotified(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
stringRedisTemplate.opsForHash().put(key, FIELD_INVALID_WORK_NOTIFIED, "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除丢失记录
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
*/
|
||||
public void clearLossRecord(Long deviceId, Long areaId) {
|
||||
String key = formatKey(deviceId, areaId);
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Redis Key
|
||||
*/
|
||||
private static String formatKey(Long deviceId, Long areaId) {
|
||||
return String.format(LOSS_KEY_PATTERN, deviceId, areaId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.viewsh.module.iot.dal.redis.clean;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 客流计数器基准值 Redis DAO
|
||||
* <p>
|
||||
* 用于维护客流计数器的基准值,支持逻辑清零和每日自动校准
|
||||
* 实际客流 = 当前计数值 - 基准值
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Repository
|
||||
public class TrafficCounterBaseRedisDAO {
|
||||
|
||||
/**
|
||||
* 基准值 Key 模式
|
||||
* <p>
|
||||
* 格式:iot:clean:traffic:base:{deviceId}
|
||||
*/
|
||||
private static final String BASE_KEY_PATTERN = "iot:clean:traffic:base:%s";
|
||||
|
||||
/**
|
||||
* 基准值的 TTL(秒)
|
||||
* <p>
|
||||
* 默认保留 7 天
|
||||
*/
|
||||
private static final int BASE_TTL_SECONDS = 604800;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* 设置基准值
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param baseValue 基准值
|
||||
*/
|
||||
public void setBaseValue(Long deviceId, Long baseValue) {
|
||||
String key = formatKey(deviceId);
|
||||
stringRedisTemplate.opsForValue().set(key, String.valueOf(baseValue),
|
||||
BASE_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基准值
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 基准值,如果不存在返回 0
|
||||
*/
|
||||
public Long getBaseValue(Long deviceId) {
|
||||
String key = formatKey(deviceId);
|
||||
String baseValueStr = stringRedisTemplate.opsForValue().get(key);
|
||||
|
||||
if (baseValueStr == null) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
return Long.parseLong(baseValueStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置基准值
|
||||
* <p>
|
||||
* 将基准值设置为 0
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
public void resetBaseValue(Long deviceId) {
|
||||
setBaseValue(deviceId, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除基准值
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
public void deleteBaseValue(Long deviceId) {
|
||||
String key = formatKey(deviceId);
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 Redis Key
|
||||
*/
|
||||
private static String formatKey(Long deviceId) {
|
||||
return String.format(BASE_KEY_PATTERN, deviceId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.viewsh.module.iot.service.integration.clean;
|
||||
|
||||
import com.viewsh.module.iot.dal.dataobject.integration.clean.CleanOrderIntegrationConfig;
|
||||
|
||||
/**
|
||||
* 保洁工单集成配置 Service
|
||||
* <p>
|
||||
* 提供运营区域设备关联配置的查询和管理能力
|
||||
* 支持本地 Redis 缓存,减少数据库查询压力
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
public interface CleanOrderIntegrationConfigService {
|
||||
|
||||
/**
|
||||
* 根据设备ID查询配置(带缓存)
|
||||
* <p>
|
||||
* 优先从 Redis 缓存读取,缓存未命中时从数据库查询并写入缓存
|
||||
* 缓存 TTL: 5 分钟
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 集成配置,如果不存在或未启用返回 null
|
||||
*/
|
||||
CleanOrderIntegrationConfig getConfigByDeviceId(Long deviceId);
|
||||
|
||||
/**
|
||||
* 根据区域ID查询所有启用的配置(带缓存)
|
||||
* <p>
|
||||
* 优先从 Redis 缓存读取
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 集成配置列表
|
||||
*/
|
||||
java.util.List<AreaDeviceConfigWrapper> getConfigsByAreaId(Long areaId);
|
||||
|
||||
/**
|
||||
* 根据区域ID和关联类型查询配置
|
||||
* <p>
|
||||
* 例如:查询某个区域的所有客流计数器配置
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @param relationType 关联类型(TRAFFIC_COUNTER/BEACON/BADGE)
|
||||
* @return 集成配置列表
|
||||
*/
|
||||
java.util.List<AreaDeviceConfigWrapper> getConfigsByAreaIdAndRelationType(Long areaId, String relationType);
|
||||
|
||||
/**
|
||||
* 清除设备配置缓存
|
||||
* <p>
|
||||
* 配置更新后调用此方法清除缓存,确保下次查询能获取最新配置
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
void evictCache(Long deviceId);
|
||||
|
||||
/**
|
||||
* 清除区域配置缓存
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
*/
|
||||
void evictAreaCache(Long areaId);
|
||||
|
||||
/**
|
||||
* 区域设备配置包装类
|
||||
* <p>
|
||||
* 包含配置数据和关联的基础信息(设备ID、区域ID、关联类型等)
|
||||
*/
|
||||
class AreaDeviceConfigWrapper {
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private Long deviceId;
|
||||
|
||||
/**
|
||||
* 设备Key
|
||||
*/
|
||||
private String deviceKey;
|
||||
|
||||
/**
|
||||
* 产品ID
|
||||
*/
|
||||
private Long productId;
|
||||
|
||||
/**
|
||||
* 产品Key
|
||||
*/
|
||||
private String productKey;
|
||||
|
||||
/**
|
||||
* 区域ID
|
||||
*/
|
||||
private Long areaId;
|
||||
|
||||
/**
|
||||
* 关联类型
|
||||
*/
|
||||
private String relationType;
|
||||
|
||||
/**
|
||||
* 集成配置
|
||||
*/
|
||||
private CleanOrderIntegrationConfig config;
|
||||
|
||||
public AreaDeviceConfigWrapper() {
|
||||
}
|
||||
|
||||
public AreaDeviceConfigWrapper(Long deviceId, String deviceKey, Long productId,
|
||||
String productKey, Long areaId, String relationType,
|
||||
CleanOrderIntegrationConfig config) {
|
||||
this.deviceId = deviceId;
|
||||
this.deviceKey = deviceKey;
|
||||
this.productId = productId;
|
||||
this.productKey = productKey;
|
||||
this.areaId = areaId;
|
||||
this.relationType = relationType;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Long getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public void setDeviceId(Long deviceId) {
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public String getDeviceKey() {
|
||||
return deviceKey;
|
||||
}
|
||||
|
||||
public void setDeviceKey(String deviceKey) {
|
||||
this.deviceKey = deviceKey;
|
||||
}
|
||||
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public String getProductKey() {
|
||||
return productKey;
|
||||
}
|
||||
|
||||
public void setProductKey(String productKey) {
|
||||
this.productKey = productKey;
|
||||
}
|
||||
|
||||
public Long getAreaId() {
|
||||
return areaId;
|
||||
}
|
||||
|
||||
public void setAreaId(Long areaId) {
|
||||
this.areaId = areaId;
|
||||
}
|
||||
|
||||
public String getRelationType() {
|
||||
return relationType;
|
||||
}
|
||||
|
||||
public void setRelationType(String relationType) {
|
||||
this.relationType = relationType;
|
||||
}
|
||||
|
||||
public CleanOrderIntegrationConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setConfig(CleanOrderIntegrationConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.viewsh.module.iot.service.integration.clean;
|
||||
|
||||
import com.viewsh.framework.common.util.json.JsonUtils;
|
||||
import com.viewsh.module.iot.dal.dataobject.integration.clean.CleanOrderIntegrationConfig;
|
||||
import com.viewsh.module.iot.dal.dataobject.integration.clean.OpsAreaDeviceRelationDO;
|
||||
import com.viewsh.module.iot.dal.mysql.integration.clean.OpsAreaDeviceRelationMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 保洁工单集成配置 Service 实现类
|
||||
*
|
||||
* @author AI
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CleanOrderIntegrationConfigServiceImpl implements CleanOrderIntegrationConfigService {
|
||||
|
||||
/**
|
||||
* 配置缓存 Key 模式
|
||||
*/
|
||||
private static final String CONFIG_DEVICE_KEY_PATTERN = "iot:clean:config:device:%s";
|
||||
private static final String CONFIG_AREA_KEY_PATTERN = "iot:clean:config:area:%s";
|
||||
|
||||
/**
|
||||
* 配置缓存 TTL(秒)
|
||||
* <p>
|
||||
* 5 分钟自动过期
|
||||
*/
|
||||
private static final int CONFIG_CACHE_TTL_SECONDS = 300;
|
||||
|
||||
@Resource
|
||||
private OpsAreaDeviceRelationMapper relationMapper;
|
||||
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public CleanOrderIntegrationConfig getConfigByDeviceId(Long deviceId) {
|
||||
log.debug("[CleanOrderConfig] 查询设备配置:deviceId={}", deviceId);
|
||||
|
||||
// 1. 尝试从 Redis 缓存读取
|
||||
String cacheKey = formatDeviceKey(deviceId);
|
||||
String cachedConfig = stringRedisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
if (cachedConfig != null) {
|
||||
log.debug("[CleanOrderConfig] 命中缓存:deviceId={}", deviceId);
|
||||
return JsonUtils.parseObject(cachedConfig, CleanOrderIntegrationConfig.class);
|
||||
}
|
||||
|
||||
// 2. 从数据库查询
|
||||
OpsAreaDeviceRelationDO relation = relationMapper.selectByDeviceId(deviceId);
|
||||
|
||||
if (relation == null) {
|
||||
log.debug("[CleanOrderConfig] 设备无关联配置:deviceId={}", deviceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!relation.getEnabled()) {
|
||||
log.debug("[CleanOrderConfig] 设备配置已禁用:deviceId={}", deviceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
CleanOrderIntegrationConfig config = relation.getConfigData();
|
||||
|
||||
// 3. 写入缓存
|
||||
if (config != null) {
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
cacheKey,
|
||||
JsonUtils.toJsonString(config),
|
||||
CONFIG_CACHE_TTL_SECONDS,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
}
|
||||
|
||||
log.debug("[CleanOrderConfig] 查询到配置:deviceId={}, areaId={}", deviceId, relation.getAreaId());
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AreaDeviceConfigWrapper> getConfigsByAreaId(Long areaId) {
|
||||
log.debug("[CleanOrderConfig] 查询区域配置:areaId={}", areaId);
|
||||
|
||||
// 区域配置暂不缓存,直接从数据库查询
|
||||
List<OpsAreaDeviceRelationDO> relations = relationMapper.selectListByAreaId(areaId);
|
||||
|
||||
return relations.stream()
|
||||
.map(this::wrapConfig)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AreaDeviceConfigWrapper> getConfigsByAreaIdAndRelationType(Long areaId, String relationType) {
|
||||
log.debug("[CleanOrderConfig] 查询区域配置:areaId={}, relationType={}", areaId, relationType);
|
||||
|
||||
List<OpsAreaDeviceRelationDO> relations = relationMapper.selectListByAreaIdAndRelationType(areaId, relationType);
|
||||
|
||||
return relations.stream()
|
||||
.map(this::wrapConfig)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictCache(Long deviceId) {
|
||||
String cacheKey = formatDeviceKey(deviceId);
|
||||
stringRedisTemplate.delete(cacheKey);
|
||||
log.info("[CleanOrderConfig] 清除设备配置缓存:deviceId={}", deviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictAreaCache(Long areaId) {
|
||||
String cacheKey = formatAreaKey(areaId);
|
||||
stringRedisTemplate.delete(cacheKey);
|
||||
log.info("[CleanOrderConfig] 清除区域配置缓存:areaId={}", areaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装配置数据
|
||||
*/
|
||||
private AreaDeviceConfigWrapper wrapConfig(OpsAreaDeviceRelationDO relation) {
|
||||
return new AreaDeviceConfigWrapper(
|
||||
relation.getDeviceId(),
|
||||
relation.getDeviceKey(),
|
||||
relation.getProductId(),
|
||||
relation.getProductKey(),
|
||||
relation.getAreaId(),
|
||||
relation.getRelationType(),
|
||||
relation.getConfigData()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化设备配置缓存 Key
|
||||
*/
|
||||
private static String formatDeviceKey(Long deviceId) {
|
||||
return String.format(CONFIG_DEVICE_KEY_PATTERN, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化区域配置缓存 Key
|
||||
*/
|
||||
private static String formatAreaKey(Long areaId) {
|
||||
return String.format(CONFIG_AREA_KEY_PATTERN, areaId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user